001: /*
002: * Copyright 2005 Patrick Gotthardt
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package com.jidesoft.swing;
017:
018: import java.awt.*;
019: import java.util.*;
020: import java.util.List;
021:
022: /**
023: * <code>StyledLabelBuilder</code> is a quick way to define StyledLabel.
024: * It provides two ways to handle the creation and modification of StyleLabels.
025: * <p/>
026: * The first is to use it as a builder (thus the name).
027: * This way is preferred if you want to create a StyledLabel with a specific
028: * format and partially generic content.
029: * Example:
030: * <pre><code>StyledLabel label = new StyledLabelBuilder()
031: * .add(file.getName())
032: * .add(" (", Font.BOLD)
033: * .add(file.getPath(), "italic") // using annotation style - see section two for information about annotations
034: * .add(")", Font.BOLD)
035: * .createLabel();</code></pre>
036: * This code would be used to create a label like "something.txt (/temp/something.txt)" with some styling (the braces would be bold, the path would be italic).
037: * In case you find yourself reusing a specific style quite often in such a label
038: * you might consider to create a style for it.
039: * This can be done with the help of the {@link #register}-methods.
040: * As an example, the code above could be rewritten like this (though it only pays off when used for creation of longer styles):
041: * <pre><code>StyledLabelBuilder builder = new StyledLabelBuilder()
042: * .register("OPERATOR", Font.BOLD, new Color(0x000052)) // use parameters
043: * .register("PATH", "italic, f:#0000CD"); // or style annotations
044: * StyledLabel label = builder
045: * .add(file.getName())
046: * .add(" (", "OPERATOR")
047: * .add(file.getPath(), "PATH,underlined") // use a style + style annotation
048: * .add(")", "OPERATOR")
049: * .createLabel();</code></pre>
050: * Note that we're using different font colors this time. It pays off as soon as you want to modify a specific group of text parts or as your styles start to get more complicated.
051: * The {@link #clear()}-method is very useful if you want to use these styles.
052: * Instead of re-creating a new builder each time, you can use the clear-method to clear the internal buffer of text without removing the previously defined styles.
053: * <p/>
054: * Let's have an example (we're going to reuse the code from above!):
055: * <pre><code>builder.clear();
056: * builder
057: * .add(file.getName())
058: * .add(" (", "OPERATOR")
059: * .add(file.getPath(), "PATH")
060: * .add(")", "OPERATOR")
061: * .configure(label);</code></pre>
062: * <p/>
063: * If we were using Java 5, we could also do this:
064: * <pre><code>// no need to call {@link #clear()} this time
065: * builder.configure(label, String.format("%s ({%s:PATH})", file.getName(), file.getPath()));</code></pre>
066: * <p/>
067: * Each of the {@link #add} and {@link #register} methods is the same as using the corresponding StyleRange-constructor directly (except that you don't have to care about its start and length).
068: * <p/>
069: * The second, even more advanced, way to use this class is in combination with an annotated string. Using the static {@link #setStyledText} or {@link #createStyledLabel} methods you can create a fully styled label from just on string. This is ideal if you need the string to be configurable or locale-specific.
070: * The usage is even more easy than the builder-approach:
071: * <code>StyledLabel label = StyledLabelBuilder.createStyledLabel("I'm your {first:bold} styled {label:italic}!");</code>
072: * In the above example, the resulting label would have a a bold "first" and an italic "label". Each annotation is started by a "{" and ended by a "}". The text you want to be styled accordingly is seperated from its annotations by a ":". If your text needs to contain a ":" itself, you need to escape it using the "\" character. The same goes for "{" that are not supposed to start an annotation. You don't need to escape the "}" at all. If it is used within the annotated string it'll be ignored. It only counts after the annotation seperator (":").
073: * There are multiply annotations available. Each annotation offers a shortcut made up from one or two of their characters. For example: We used "bold" and "italic" in the example above, but we could've used "b" and "i" instead.
074: * It is also possible to combine multiple styles by seperating them with a ",". As an example:
075: * <code>{This text is bold, italic and blue:b,i,f:blue}</code>
076: * Instead of writing "b,i" you can also write "bi" or "bolditalic".
077: * This example brings us to colors. They've to be started with "f" or "font" for the font-color or "l" or "line" for the line-color or "b" or "background" for the background color.
078: * There are a lot of ways to specify a color. You may use its HTML name (as I did in the above example) or any of these:
079: * f:(0,0,255)
080: * f:#00F
081: * l:#0000FF
082: * l:0x0000FF
083: * The "#00F" notation is just like it is in CSS. It is the same as if you had written "#0000FF".
084: * You can get and modify the map of color-names the parser is using with the static {@link #getColorNamesMap()}-method.
085: * <p/>
086: * You saw some styles above. Here is a complete list of styles and its shortcut.
087: * <p/>
088: * <b>Font styles</b>
089: * <ul>
090: * <li>plain or p
091: * <li>bold or b
092: * <li>italic or i
093: * <li>bolditalic or bi
094: * </ul>
095: * <b>Additional styles</b>
096: * <ul>
097: * <li>strike or s
098: * <li>doublestrike or ds
099: * <li>waved or w
100: * <li>underlined or u
101: * <li>dotted or d
102: * <li>superscript or sp
103: * <li>subscript or sb
104: * </ul>
105: * <p/>
106: *
107: * @author Patrick Gotthardt
108: */
109: public class StyledLabelBuilder {
110: private StringBuffer buffer;
111: private List ranges;
112: private int start;
113: private Map styles;
114:
115: public StyledLabelBuilder() {
116: buffer = new StringBuffer();
117: ranges = new ArrayList();
118: styles = new HashMap();
119: start = 0;
120: }
121:
122: public void clear() {
123: buffer.delete(0, buffer.length());
124: ranges.clear();
125: start = 0;
126: }
127:
128: public StyledLabelBuilder register(String text, Color fontColor) {
129: styles.put(text, new StyleRange(fontColor));
130: return this ;
131: }
132:
133: public StyledLabelBuilder register(String text, int fontStyle) {
134: styles.put(text, new StyleRange(fontStyle));
135: return this ;
136: }
137:
138: public StyledLabelBuilder register(String text, int fontStyle,
139: Color fontColor) {
140: styles.put(text, new StyleRange(fontStyle, fontColor));
141: return this ;
142: }
143:
144: public StyledLabelBuilder register(String text, int fontStyle,
145: Color fontColor, int additionalStyle) {
146: styles.put(text, new StyleRange(start, text.length(),
147: fontStyle, fontColor, additionalStyle));
148: return this ;
149: }
150:
151: public StyledLabelBuilder register(String text, int fontStyle,
152: Color fontColor, int additionalStyle, Color lineColor) {
153: styles.put(text, new StyleRange(start, text.length(),
154: fontStyle, fontColor, additionalStyle, lineColor));
155: return this ;
156: }
157:
158: public StyledLabelBuilder register(String text, int fontStyle,
159: Color fontColor, int additionalStyle, Color lineColor,
160: Stroke lineStroke) {
161: styles.put(text, new StyleRange(start, text.length(),
162: fontStyle, fontColor, additionalStyle, lineColor,
163: lineStroke));
164: return this ;
165: }
166:
167: public StyledLabelBuilder register(String text, int fontStyle,
168: Color fontColor, int additionalStyle, Color lineColor,
169: Stroke lineStroke, float fontShrinkRatio) {
170: styles.put(text, new StyleRange(start, text.length(),
171: fontStyle, fontColor, additionalStyle, lineColor,
172: lineStroke, fontShrinkRatio));
173: return this ;
174: }
175:
176: public StyledLabelBuilder register(String text, int fontStyle,
177: int additionalStyle) {
178: styles.put(text, new StyleRange(start, text.length(),
179: fontStyle, additionalStyle));
180: return this ;
181: }
182:
183: public StyledLabelBuilder register(String text, int fontStyle,
184: int additionalStyle, float fontShrinkRatio) {
185: styles.put(text, new StyleRange(start, text.length(),
186: fontStyle, additionalStyle, fontShrinkRatio));
187: return this ;
188: }
189:
190: public StyledLabelBuilder register(String text, String format) {
191: ParsedStyleResult result = parseStyleAnnotation(format
192: .toCharArray(), 0, this );
193: styles.put(text, new StyleRange(result.fontStyle,
194: result.fontColor, result.backgroundColor,
195: result.additionalStyle, result.lineColor));
196: return this ;
197: }
198:
199: //
200:
201: public StyledLabelBuilder add(String text) {
202: buffer.append(text);
203: start += text.length();
204: return this ;
205: }
206:
207: public StyledLabelBuilder add(String text, Color fontColor) {
208: ranges.add(new StyleRange(start, text.length(), fontColor));
209: return add(text);
210: }
211:
212: public StyledLabelBuilder add(String text, int fontStyle) {
213: ranges.add(new StyleRange(start, text.length(), fontStyle));
214: return add(text);
215: }
216:
217: public StyledLabelBuilder add(String text, int fontStyle,
218: Color fontColor) {
219: ranges.add(new StyleRange(start, text.length(), fontStyle,
220: fontColor));
221: return add(text);
222: }
223:
224: public StyledLabelBuilder add(String text, int fontStyle,
225: Color fontColor, int additionalStyle) {
226: ranges.add(new StyleRange(start, text.length(), fontStyle,
227: fontColor, additionalStyle));
228: return add(text);
229: }
230:
231: public StyledLabelBuilder add(String text, int fontStyle,
232: Color fontColor, int additionalStyle, Color lineColor) {
233: ranges.add(new StyleRange(start, text.length(), fontStyle,
234: fontColor, additionalStyle, lineColor));
235: return add(text);
236: }
237:
238: public StyledLabelBuilder add(String text, int fontStyle,
239: Color fontColor, Color backgroundColor,
240: int additionalStyle, Color lineColor) {
241: ranges
242: .add(new StyleRange(start, text.length(), fontStyle,
243: fontColor, backgroundColor, additionalStyle,
244: lineColor));
245: return add(text);
246: }
247:
248: public StyledLabelBuilder add(String text, int fontStyle,
249: Color fontColor, Color backgroundColor,
250: int additionalStyle, Color lineColor, Stroke lineStroke) {
251: ranges.add(new StyleRange(start, text.length(), fontStyle,
252: fontColor, backgroundColor, additionalStyle, lineColor,
253: lineStroke));
254: return add(text);
255: }
256:
257: public StyledLabelBuilder add(String text, int fontStyle,
258: Color fontColor, int additionalStyle, Color lineColor,
259: Stroke lineStroke, float fontShrinkRatio) {
260: ranges.add(new StyleRange(start, text.length(), fontStyle,
261: fontColor, additionalStyle, lineColor, lineStroke,
262: fontShrinkRatio));
263: return add(text);
264: }
265:
266: public StyledLabelBuilder add(String text, String style) {
267: StyleRange range = (StyleRange) styles.get(style);
268: // not a stored style, thus it might be an annotation
269: if (range == null) {
270: ParsedStyleResult result = parseStyleAnnotation(style
271: .toCharArray(), 0, this );
272: return add(text, result.fontStyle, result.fontColor,
273: result.backgroundColor, result.additionalStyle,
274: result.lineColor);
275: }
276: return add(text, range.getFontStyle(), range.getFontColor(),
277: range.getAdditionalStyle(), range.getLineColor(), range
278: .getLineStroke(), range.getFontShrinkRatio());
279: }
280:
281: public StyledLabelBuilder add(String text, int fontStyle,
282: int additionalStyle) {
283: ranges.add(new StyleRange(start, text.length(), fontStyle,
284: additionalStyle));
285: return add(text);
286: }
287:
288: public StyledLabelBuilder add(String text, int fontStyle,
289: int additionalStyle, float fontShrinkRatio) {
290: ranges.add(new StyleRange(start, text.length(), fontStyle,
291: additionalStyle, fontShrinkRatio));
292: return add(text);
293: }
294:
295: public StyledLabel configure(StyledLabel label, String style) {
296: StyledLabelBuilder.setStyledText(label, style, this );
297: return label;
298: }
299:
300: public StyledLabel configure(StyledLabel label) {
301: label.setText(buffer.toString());
302: int size = ranges.size();
303: for (int i = 0; i < size; i++) {
304: label.addStyleRange((StyleRange) ranges.get(i));
305: }
306: return label;
307: }
308:
309: public StyledLabel createLabel() {
310: return configure(new StyledLabel());
311: }
312:
313: // complex part
314: public static StyledLabel createStyledLabel(String text) {
315: StyledLabel label = new StyledLabel();
316: setStyledText(label, text);
317: return label;
318: }
319:
320: public static void setStyledText(StyledLabel label, String text) {
321: setStyledText(label, text.toCharArray());
322: }
323:
324: private static void setStyledText(StyledLabel label, String text,
325: StyledLabelBuilder builder) {
326: setStyledText(label, text.toCharArray(), builder);
327: }
328:
329: public static void setStyledText(StyledLabel label, char[] text) {
330: setStyledText(label, text, null);
331: }
332:
333: private static void setStyledText(StyledLabel label, char[] text,
334: StyledLabelBuilder builder) {
335: StringBuffer labelText = new StringBuffer(text.length);
336: boolean escaped = false;
337: for (int i = 0; i < text.length; i++) {
338: if (escaped) {
339: labelText.append(text[i]);
340: escaped = false;
341: continue;
342: }
343: switch (text[i]) {
344: case '{':
345: ParsedStyleResult result = parseStylePart(text, i + 1,
346: builder);
347: int realIndex = labelText.length();
348: labelText.append(result.text);
349: label.addStyleRange(new StyleRange(realIndex,
350: result.text.length(), result.fontStyle,
351: result.fontColor, result.backgroundColor,
352: result.additionalStyle, result.lineColor));
353: i = result.endOffset;
354: break;
355: case '\\':
356: escaped = true;
357: break;
358: default:
359: labelText.append(text[i]);
360: break;
361: }
362: }
363: label.setText(labelText.toString());
364: }
365:
366: private static ParsedStyleResult parseStylePart(char[] text,
367: int start, StyledLabelBuilder builder) {
368: ParsedStyleResult result = new ParsedStyleResult();
369: int findIndex, i = start;
370: // find end of text first
371: findIndex = findNext(text, ':', i);
372: result.text = createTrimmedString(text, i, findIndex - 1);
373: return parseStyleAnnotation(text, findIndex + 1, builder,
374: result);
375: }
376:
377: private static ParsedStyleResult parseStyleAnnotation(char[] text,
378: int start, StyledLabelBuilder builder) {
379: ParsedStyleResult result = new ParsedStyleResult();
380: return parseStyleAnnotation(text, start, builder, result);
381: }
382:
383: private static ParsedStyleResult parseStyleAnnotation(char[] text,
384: int findIndex, StyledLabelBuilder builder,
385: ParsedStyleResult result) {
386: int i = findIndex;
387: char[] importantChars = { ',', '}' };
388: boolean endOfTag = false;
389: while (i < text.length && !endOfTag) {
390: findIndex = findNextOf(text, importantChars, i);
391: String style;
392: if (findIndex == -1 || text[findIndex] == '}') {
393: endOfTag = true;
394: }
395: style = createTrimmedString(text, i,
396: findIndex == -1 ? text.length - 1 : findIndex - 1);
397: // start with colors first - they're easiest to guess
398: int colonIndex = style.indexOf(':');
399: if (colonIndex != -1) {
400: String color = style.substring(colonIndex + 1);
401: // the (r,g,b)-construct allows "," thus we'll have to handle it here!
402: if (color.length() > 1) {
403: if (color.charAt(0) == '(') {
404: findIndex = findNext(text, ')', i + colonIndex
405: + 1);
406: style = createTrimmedString(text, i,
407: findIndex + 1);
408: color = style.substring(colonIndex + 1);
409: // we need to do some specific checking here
410: if (text[findIndex + 1] == '}') {
411: endOfTag = true;
412: }
413: // in any case: the cursor needs to be moved forward by one
414: findIndex++;
415: }
416: if (style.charAt(0) == 'f') {
417: result.fontColor = toColor(color);
418: } else if (style.charAt(0) == 'b') {
419: result.backgroundColor = toColor(color);
420: } else {
421: result.lineColor = toColor(color);
422: }
423: }
424: } else {
425: // no color, now it's getting though
426: if (style.equals("plain") || style.equals("p")) {
427: result.fontStyle = Font.PLAIN;
428:
429: } else if (style.equals("bold") || style.equals("b")) {
430: result.fontStyle = Font.BOLD;
431:
432: } else if (style.equals("italic") || style.equals("i")) {
433: result.fontStyle = Font.ITALIC;
434:
435: } else if (style.equals("bolditalic")
436: || style.equals("bi")) {
437: result.fontStyle = Font.ITALIC + Font.BOLD;
438:
439: } else if (style.equals("strike") || style.equals("s")) {
440: result.additionalStyle |= StyleRange.STYLE_STRIKE_THROUGH;
441:
442: } else if (style.equals("doublestrike")
443: || style.equals("ds")) {
444: result.additionalStyle |= StyleRange.STYLE_DOUBLE_STRIKE_THROUGH;
445:
446: } else if (style.equals("waved") || style.equals("w")) {
447: result.additionalStyle |= StyleRange.STYLE_WAVED;
448:
449: } else if (style.equals("underlined")
450: || style.equals("u")) {
451: result.additionalStyle |= StyleRange.STYLE_UNDERLINED;
452:
453: } else if (style.equals("dotted") || style.equals("d")) {
454: result.additionalStyle |= StyleRange.STYLE_DOTTED;
455:
456: } else if (style.equals("superscript")
457: || style.equals("sp")) {
458: result.additionalStyle |= StyleRange.STYLE_SUPERSCRIPT;
459:
460: } else if (style.equals("subscipt")
461: || style.equals("sb")) {
462: result.additionalStyle |= StyleRange.STYLE_SUBSCRIPT;
463: } else if (builder != null
464: && builder.styles.containsKey(style)) {
465: StyleRange range = (StyleRange) builder.styles
466: .get(style);
467: result.fontStyle = range.getFontStyle();
468: result.fontColor = range.getFontColor();
469: result.backgroundColor = range.getBackgroundColor();
470: result.additionalStyle = range.getAdditionalStyle();
471: result.lineColor = range.getLineColor();
472: } else if (style.length() > 0) {
473: System.err.println("Unknown style '" + style + "'");
474: }
475: }
476: i = findIndex + 1;
477: }
478: result.endOffset = i - 1;
479: // done, return
480: return result;
481: }
482:
483: /**
484: * Can be:
485: * (255, 0, 0)
486: * #FF0000
487: * #F00
488: * 0xFF0000
489: * red
490: */
491: private static Color toColor(String str) {
492: switch (str.charAt(0)) {
493: case '(':
494: int red,
495: green,
496: blue;
497: int index;
498:
499: red = nextColorInt(str, 1);
500:
501: index = str.indexOf(',');
502: green = nextColorInt(str, index + 1);
503:
504: index = str.indexOf(',', index + 1);
505: blue = nextColorInt(str, index + 1);
506:
507: return new Color(red, green, blue);
508: case '#':
509: // Shorthand?
510: if (str.length() == 4) {
511: return new Color(getShorthandValue(str.charAt(1)),
512: getShorthandValue(str.charAt(2)),
513: getShorthandValue(str.charAt(3)));
514: } else {
515: return new Color(Integer.parseInt(str.substring(1), 16));
516: }
517: case '0':
518: return new Color(Integer.parseInt(str.substring(2), 16));
519: default:
520: return (Color) colorNamesMap.get(str);
521: }
522: }
523:
524: private static int nextColorInt(String str, int index) {
525: // start with adjusting the start index
526: while (index < str.length()) {
527: char c = str.charAt(index);
528: // a digit?
529: if ('0' <= c && c <= '9') {
530: break;
531: } else {
532: index++;
533: }
534: }
535: // that's only the maximum limit!
536: int colorLength = index;
537: for (; colorLength < index + 3; colorLength++) {
538: char c = str.charAt(colorLength);
539: // not a digit?
540: if (c < '0' || '9' < c) {
541: break;
542: }
543: }
544: return Integer.parseInt(str.substring(index, colorLength));
545: }
546:
547: private static int getShorthandValue(char c) {
548: c = Character.toUpperCase(c);
549: if ('A' <= c && c <= 'F') {
550: return colorShorthandTable[c - 'A' + 10];
551: }
552: return colorShorthandTable[c - '0'];
553: }
554:
555: private static int[] colorShorthandTable = { 0x00, 0x11, 0x22,
556: 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC,
557: 0xDD, 0xEE, 0xFF };
558:
559: private static Map colorNamesMap;
560:
561: static {
562: colorNamesMap = new TreeMap();
563: colorNamesMap.put("white", new Color(0xFFFFFF));
564: colorNamesMap.put("lightGray", new Color(0xC0C0C0));
565: colorNamesMap.put("gray", new Color(0x808080));
566: colorNamesMap.put("darkGray", new Color(0x404040));
567: colorNamesMap.put("black", new Color(0x000000));
568: colorNamesMap.put("red", new Color(0xFF0000));
569: colorNamesMap.put("pink", new Color(0xFFAFAF));
570: colorNamesMap.put("orange", new Color(0xFFC800));
571: colorNamesMap.put("yellow", new Color(0xFFFF00));
572: colorNamesMap.put("green", new Color(0x00FF00));
573: colorNamesMap.put("magenta", new Color(0xFF00FF));
574: colorNamesMap.put("cyan", new Color(0x00FFFF));
575: colorNamesMap.put("blue", new Color(0x0000FF));
576: }
577:
578: public static Map getColorNamesMap() {
579: return colorNamesMap;
580: }
581:
582: private static String createTrimmedString(char[] text, int start,
583: int end) {
584: for (; (text[start] == ' ' || text[start] == '\t')
585: && start < text.length; start++)
586: ;
587: for (; (text[end] == ' ' || text[end] == '\t') && start < end; end--)
588: ;
589: // need to remove escape chars
590: StringBuffer buffer = new StringBuffer(end - start);
591: boolean escaped = false;
592: for (int i = start; i <= end; i++) {
593: if (text[i] == '\\' && !escaped) {
594: escaped = true;
595: } else {
596: buffer.append(text[i]);
597: if (escaped) {
598: escaped = false;
599: }
600: }
601: }
602: return buffer.toString();
603: }
604:
605: private static int findNextOf(char[] text, char[] c, int start) {
606: boolean escaped = false;
607: for (int i = start; i < text.length; i++) {
608: if (escaped) {
609: escaped = false;
610: continue;
611: }
612: if (text[i] == '\\') {
613: escaped = true;
614: } else {
615: for (int j = 0; j < c.length; j++) {
616: if (text[i] == c[j]) {
617: return i;
618: }
619: }
620: }
621: }
622: return -1;
623: }
624:
625: private static int findNext(char[] text, char c, int start) {
626: boolean escaped = false;
627: for (int i = start; i < text.length; i++) {
628: if (escaped) {
629: escaped = false;
630: continue;
631: }
632: if (text[i] == '\\') {
633: escaped = true;
634: } else if (text[i] == c) {
635: return i;
636: }
637: }
638: return -1;
639: }
640:
641: private static class ParsedStyleResult {
642: String text;
643: int endOffset;
644: int fontStyle = Font.PLAIN;
645: Color fontColor = null, lineColor = null,
646: backgroundColor = null;
647: int additionalStyle = 0;
648: }
649: }
|