File Source: Styliser.java
/*
P/P * Method: com.dmdirc.ui.messages.Styliser$1__static_init
*/
1 /*
2 * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 package com.dmdirc.ui.messages;
24
25 import com.dmdirc.actions.ActionManager;
26 import com.dmdirc.actions.CoreActionType;
27 import com.dmdirc.interfaces.ConfigChangeListener;
28 import com.dmdirc.config.IdentityManager;
29 import com.dmdirc.logger.ErrorLevel;
30 import com.dmdirc.logger.Logger;
31
32 import java.awt.Color;
33 import java.util.Locale;
34
35 import javax.swing.UIManager;
36 import javax.swing.text.BadLocationException;
37 import javax.swing.text.DefaultStyledDocument;
38 import javax.swing.text.SimpleAttributeSet;
39 import javax.swing.text.StyleConstants;
40 import javax.swing.text.StyledDocument;
41
42 /**
43 * The styliser applies IRC styles to text. Styles are indicated by various
44 * control codes which are a de-facto IRC standard.
45 * @author chris
46 */
/*
P/P * Method: bool access$002(bool)
*
* Postconditions:
* return_value == x0
* init'ed(return_value)
* styleLinks == return_value
*/
47 public final class Styliser {
48
49 /** The character used for marking up bold text. */
50 public static final char CODE_BOLD = 2;
51 /** The character used for marking up coloured text. */
52 public static final char CODE_COLOUR = 3;
53 /** The character used for marking up coloured text (using hex). */
54 public static final char CODE_HEXCOLOUR = 4;
55 /** Character used to indicate hyperlinks. */
56 public static final char CODE_HYPERLINK = 5;
57 /** Character used to indicate channel links. */
58 public static final char CODE_CHANNEL = 6;
59 /** Character used to indicate smilies. */
60 public static final char CODE_SMILIE = 7;
61 /** The character used for stopping all formatting. */
62 public static final char CODE_STOP = 15;
63 /** Character used to indicate nickname links. */
64 public static final char CODE_NICKNAME = 16;
65 /** The character used for marking up fixed pitch text. */
66 public static final char CODE_FIXED = 17;
67 /** The character used for negating control codes. */
68 public static final char CODE_NEGATE = 18;
69 /** The character used for marking up italic text. */
70 public static final char CODE_ITALIC = 29;
71 /** The character used for marking up underlined text. */
72 public static final char CODE_UNDERLINE = 31;
73
74 /** Internal chars. */
/*
P/P * Method: com.dmdirc.ui.messages.Styliser__static_init
*
* Presumptions:
* com.dmdirc.config.IdentityManager:getGlobalConfig(...)@127 != null
* com.dmdirc.config.IdentityManager:getGlobalConfig(...)@131 != null
*
* Postconditions:
* INTERNAL_CHARS == &java.lang.StringBuilder:toString(...)
* java.lang.StringBuilder:toString(...)._tainted == 0
* init'ed(styleLinks)
*/
75 private static final String INTERNAL_CHARS = String.valueOf(CODE_HYPERLINK)
76 + CODE_NICKNAME + CODE_CHANNEL + CODE_SMILIE;
77
78 /** Regexp to match characters which shouldn't be used in channel links. */
79 private static final String RESERVED_CHARS = "[^\\s" + CODE_BOLD + CODE_COLOUR
80 + CODE_STOP + CODE_HEXCOLOUR + CODE_FIXED + CODE_ITALIC
81 + CODE_UNDERLINE + CODE_CHANNEL + CODE_NICKNAME + CODE_NEGATE + "\"]";
82
83 /** Defines all characters treated as trailing punctuation that are illegal in URLs. */
84 private static final String URL_PUNCT_ILLEGAL = "\"";
85
86 /** Defines all characters treated as trailing punctuation that're legal in URLs. */
87 private static final String URL_PUNCT_LEGAL = "';:!,\\.\\?";
88
89 /** Defines all trailing punctuation. */
90 private static final String URL_PUNCT = URL_PUNCT_ILLEGAL + URL_PUNCT_LEGAL;
91
92 /** Defines all characters allowed in URLs that aren't treated as trailing punct. */
93 private static final String URL_NOPUNCT = "a-z0-9$\\-_@&\\+\\*\\(\\)=/#%~";
94
95 /** Defines all characters allowed in URLs per W3C specs. */
96 private static final String URL_CHARS = "[" + URL_PUNCT_LEGAL + URL_NOPUNCT
97 + "]*[" + URL_NOPUNCT + "]+[" + URL_PUNCT_LEGAL + URL_NOPUNCT + "]*";
98
99 /** The regular expression to use for marking up URLs. */
100 private static final String URL_REGEXP = "(?i)([a-z+]+://" + URL_CHARS
101 + "|(?<![a-z0-9:/])www\\." + URL_CHARS + ")";
102
103 /** Regular expression for intelligent handling of closing brackets. */
104 private static final String URL_INT1 = "(\\([^\\)" + CODE_HYPERLINK
105 + "]*(?:" + CODE_HYPERLINK + "[^" + CODE_HYPERLINK + "]*"
106 + CODE_HYPERLINK + ")?[^\\)" + CODE_HYPERLINK + "]*" + CODE_HYPERLINK
107 + "[^" + CODE_HYPERLINK + "]+)(\\)['\";:!,\\.\\)]*)" + CODE_HYPERLINK;
108
109 /** Regular expression for intelligent handling of trailing single and double quotes. */
110 private static final String URL_INT2 = "(^(?:[^" + CODE_HYPERLINK + "]+|"
111 + CODE_HYPERLINK + "[^" + CODE_HYPERLINK + "]" + CODE_HYPERLINK + "))(['\"])([^"
112 + CODE_HYPERLINK + "]*?" + CODE_HYPERLINK + "[^" + CODE_HYPERLINK
113 + "]+)(\\1[" + URL_PUNCT + "]*)" + CODE_HYPERLINK;
114
115 /** Regular expression for intelligent handling of surrounding quotes. */
116 private static final String URL_INT3 = "(['\"])(" + CODE_HYPERLINK
117 + "[^" + CODE_HYPERLINK + "]+?)(\\1[^" + CODE_HYPERLINK + "]*)" + CODE_HYPERLINK;
118
119 /** Regular expression for intelligent handling of trailing punctuation. */
120 private static final String URL_INT4 = "(" + CODE_HYPERLINK
121 + "[^" + CODE_HYPERLINK + "]+?)([" + URL_PUNCT + "]?)" + CODE_HYPERLINK;
122
123 /** The regular expression to use for marking up channels. */
124 private static final String URL_CHANNEL = "(?i)(?<![^\\s])([#&]" + RESERVED_CHARS + "+)";
125
126 /** Whether or not we should style links. */
127 private static boolean styleLinks
128 = IdentityManager.getGlobalConfig().getOptionBool("ui", "stylelinks");
129
130 static {
131 IdentityManager.getGlobalConfig().addChangeListener("ui", "stylelinks",
/*
P/P * Method: void com.dmdirc.ui.messages.Styliser$1()
*/
132 new ConfigChangeListener() {
133 @Override
134 public void configChanged(final String domain, final String key) {
/*
P/P * Method: void configChanged(String, String)
*
* Presumptions:
* com.dmdirc.config.IdentityManager:getGlobalConfig(...)@135 != null
*
* Postconditions:
* init'ed(com/dmdirc/ui/messages/Styliser.styleLinks)
*/
135 Styliser.styleLinks
136 = IdentityManager.getGlobalConfig().getOptionBool("ui", "stylelinks");
137 }
138 });
139 }
140
141 /** Creates a new instance of Styliser. */
/*
P/P * Method: void com.dmdirc.ui.messages.Styliser()
*/
142 private Styliser() {
143 }
144
145 /**
146 * Stylises the specified strings and adds them to the specified document.
147 *
148 * @param styledDoc Document to add the styled strings to
149 * @param strings The lines to be stylised
150 */
151 public static void addStyledString(final StyledDocument styledDoc, final String[] strings) {
/*
P/P * Method: void addStyledString(StyledDocument, String[])
*
* Preconditions:
* strings != null
* strings.length <= 232-1
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS.length >= 1
* (soft) init'ed(com/dmdirc/ui/messages/ColourManager.IRC_COLOURS[...])
* (soft) strings[...] != null
* (soft) init'ed(styleLinks)
* (soft) styledDoc != null
*
* Presumptions:
* chars.length@153 <= 232-1
* init'ed(com.dmdirc.actions.CoreActionType.CLIENT_STRING_STYLED)
* init'ed(com.dmdirc.logger.ErrorLevel.MEDIUM)
* javax.swing.text.StyledDocument:getLength(...)@187 - javax.swing.text.StyledDocument:getLength(...)@162 in {-231..232-1}
*/
152 for (int i = 0; i < strings.length; i++) {
153 final char[] chars = strings[i].toCharArray();
154
155 for (int j = 0; j < chars.length; j++) {
156 if (chars[j] == 65533) {
157 chars[j] = '?';
158 }
159 }
160
161 try {
162 final int ooffset = styledDoc.getLength();
163 int offset = ooffset;
164 int position = 0;
165
166 String target = doSmilies(doLinks(new String(chars).replaceAll(INTERNAL_CHARS, "")));
167
168 target = target.replaceAll(URL_CHANNEL, CODE_CHANNEL + "$0" + CODE_CHANNEL);
169
170 final SimpleAttributeSet attribs = new SimpleAttributeSet();
171 attribs.addAttribute("DefaultFontFamily", UIManager.getFont("TextPane.font"));
172
173 while (position < target.length()) {
174 final String next = readUntilControl(target.substring(position));
175
176 styledDoc.insertString(offset, next, attribs);
177
178 position += next.length();
179 offset += next.length();
180
181 if (position < target.length()) {
182 position += readControlChars(target.substring(position),
183 attribs, position == 0);
184 }
185 }
186
187 ActionManager.processEvent(CoreActionType.CLIENT_STRING_STYLED,
188 null, styledDoc, ooffset, styledDoc.getLength() - ooffset);
189
190 } catch (BadLocationException ex) {
191 Logger.userError(ErrorLevel.MEDIUM,
192 "Unable to insert styled string: " + ex.getMessage());
193 }
194 }
195 }
196
197 /**
198 * Stylises the specified string.
199 *
200 * @param strings The line to be stylised
201 *
202 * @return StyledDocument for the inputted strings
203 */
204 public static StyledDocument getStyledString(final String[] strings) {
/*
P/P * Method: StyledDocument getStyledString(String[])
*
* Preconditions:
* strings != null
* strings.length <= 232-1
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS.length >= 1
* (soft) init'ed(com/dmdirc/ui/messages/ColourManager.IRC_COLOURS[...])
* (soft) strings[...] != null
* (soft) init'ed(styleLinks)
*
* Postconditions:
* return_value == &new DefaultStyledDocument(getStyledString#1)
* new DefaultStyledDocument(getStyledString#1) num objects == 1
*/
205 final StyledDocument styledDoc = new DefaultStyledDocument();
206
207 addStyledString(styledDoc, strings);
208
209 return styledDoc;
210 }
211
212 /**
213 * Applies the hyperlink styles and intelligent linking regexps to the
214 * target.
215 *
216 * @param string The string to be linked
217 * @return A copy of the string with hyperlinks marked up
218 */
219 public static String doLinks(final String string) {
/*
P/P * Method: String doLinks(String)
*
* Preconditions:
* string != null
*
* Postconditions:
* return_value != null
*
* Test Vectors:
* java.lang.String:equals(...)@226: {1}, {0}
* java.lang.String:matches(...)@222: {0}, {1}
*/
220 String target = string;
221
222 if (target.matches(".*" + URL_REGEXP + ".*")) {
223 target = target.replaceAll(URL_REGEXP, CODE_HYPERLINK + "$0" + CODE_HYPERLINK);
224 String target2 = "";
225
226 for (int j = 0; j < 5 && !target.equals(target2); j++) {
227 target2 = target;
228 target = target
229 .replaceAll(URL_INT1, "$1" + CODE_HYPERLINK + "$2")
230 .replaceAll(URL_INT2, "$1$2$3" + CODE_HYPERLINK + "$4")
231 .replaceAll(URL_INT3, "$1$2" + CODE_HYPERLINK + "$3")
232 .replaceAll(URL_INT4, "$1" + CODE_HYPERLINK + "$2");
233 }
234 }
235
236 return target;
237 }
238
239 /**
240 * Applies the smilie styles to the target.
241 *
242 * @param string The string to be smilified
243 * @return A copy of the string with smilies marked up
244 * @since 0.6.3m1
245 */
246 public static String doSmilies(final String string) {
247 // TODO: read types from config. Check if they're enabled.
248
/*
P/P * Method: String doSmilies(String)
*
* Preconditions:
* string != null
*
* Postconditions:
* return_value != null
*/
249 return string.replaceAll("(\\s|^):[\\\\/](?=\\s|$)", "$1" + CODE_SMILIE + ":/"
250 + CODE_SMILIE);
251 }
252
253 /**
254 * Strips all recognised control codes from the input string.
255 * @param input the String to be stripped
256 * @return a copy of the input with control codes removed
257 */
258 public static String stipControlCodes(final String input) {
/*
P/P * Method: String stipControlCodes(String)
*
* Preconditions:
* input != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS.length >= 1
* (soft) init'ed(com/dmdirc/ui/messages/ColourManager.IRC_COLOURS[...])
* (soft) init'ed(styleLinks)
*
* Postconditions:
* init'ed(java.lang.String:concat(...)._tainted)
* return_value in Addr_Set{&"",&java.lang.String:concat(...)}
*/
259 int position = 0;
260 String output = "";
261
262 while (position < input.length()) {
263 final String next = readUntilControl(input.substring(position));
264
265 output = output.concat(next);
266
267 position += next.length();
268
269 if (position < input.length()) {
270 position += readControlChars(input.substring(position),
271 new SimpleAttributeSet(), position == 0);
272 }
273 }
274
275 return output;
276 }
277
278 /**
279 * Returns a substring of the input string such that no control codes are present
280 * in the output. If the returned value isn't the same as the input, then the
281 * character immediately after is a control character.
282 * @param input The string to read from
283 * @return A substring of the input containing no control characters
284 */
285 static String readUntilControl(final String input) {
/*
P/P * Method: String readUntilControl(String)
*
* Preconditions:
* input != null
*
* Postconditions:
* java.lang.String:substring(...)._tainted == input._tainted
* init'ed(java.lang.String:substring(...)._tainted)
* return_value == &java.lang.String:substring(...)
*/
286 int pos = input.length();
287
288 pos = checkChar(pos, input.indexOf(CODE_BOLD));
289 pos = checkChar(pos, input.indexOf(CODE_UNDERLINE));
290 pos = checkChar(pos, input.indexOf(CODE_STOP));
291 pos = checkChar(pos, input.indexOf(CODE_COLOUR));
292 pos = checkChar(pos, input.indexOf(CODE_HEXCOLOUR));
293 pos = checkChar(pos, input.indexOf(CODE_ITALIC));
294 pos = checkChar(pos, input.indexOf(CODE_FIXED));
295 pos = checkChar(pos, input.indexOf(CODE_HYPERLINK));
296 pos = checkChar(pos, input.indexOf(CODE_NICKNAME));
297 pos = checkChar(pos, input.indexOf(CODE_CHANNEL));
298 pos = checkChar(pos, input.indexOf(CODE_SMILIE));
299 pos = checkChar(pos, input.indexOf(CODE_NEGATE));
300
301 return input.substring(0, pos);
302 }
303
304 /**
305 * Helper function used in readUntilControl. Checks if i is a valid index of
306 * the string (i.e., it's not -1), and then returns the minimum of pos and i.
307 * @param pos The current position in the string
308 * @param i The index of the first occurance of some character
309 * @return The new position (see implementation)
310 */
311 private static int checkChar(final int pos, final int i) {
/*
P/P * Method: int checkChar(int, int)
*
* Postconditions:
* return_value == One-of{i, pos}
* init'ed(return_value)
*
* Test Vectors:
* i: {-1}, {-231..-2, 0..232-2}
* pos - i: {-6_442_450_943..0}, {1..232}
*/
312 if (i < pos && i != -1) { return i; }
313 return pos;
314 }
315
316 /**
317 * Reads the first control character from the input string (and any arguments
318 * it takes), and applies it to the specified attribute set.
319 * @return The number of characters read as control characters
320 * @param string The string to read from
321 * @param attribs The attribute set that new attributes will be applied to
322 * @param isStart Whether this is at the start of the string or not
323 */
324 private static int readControlChars(final String string,
325 final SimpleAttributeSet attribs, final boolean isStart) {
/*
P/P * Method: int readControlChars(String, SimpleAttributeSet, bool)
*
* Preconditions:
* attribs != null
* string != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS.length >= 1
* (soft) init'ed(com/dmdirc/ui/messages/ColourManager.IRC_COLOURS[...])
* (soft) init'ed(styleLinks)
*
* Presumptions:
* init'ed(java.lang.Boolean.TRUE)
* init'ed(javax.swing.text.StyleConstants$FontConstants.Bold)
* init'ed(javax.swing.text.StyleConstants$FontConstants.FontFamily)
* init'ed(javax.swing.text.StyleConstants$FontConstants.Italic)
* init'ed(javax.swing.text.StyleConstants$FontConstants.Underline)
*
* Postconditions:
* return_value in {0..7, 14}
*
* Test Vectors:
* isStart: {0}, {1}
* java.lang.String:charAt(...)@329: {0,1, 3..216-1}, {2}
* java.lang.String:charAt(...)@338: {0..30, 32..216-1}, {31}
* java.lang.String:charAt(...)@347: {0..28, 30..216-1}, {29}
* java.lang.String:charAt(...)@356: {0..4, 6..216-1}, {5}
* java.lang.String:charAt(...)@371: {0..5, 7..216-1}, {6}
* java.lang.String:charAt(...)@383: {0..15, 17..216-1}, {16}
* java.lang.String:charAt(...)@395: {0..16, 18..216-1}, {17}
* java.lang.String:charAt(...)@409: {0..14, 16..216-1}, {15}
* java.lang.String:charAt(...)@418: {0..2, 4..216-1}, {3}
* ...
*/
326 boolean isNegated = attribs.containsAttribute("NegateControl", Boolean.TRUE);
327
328 // Bold
329 if (string.charAt(0) == CODE_BOLD) {
330 if (!isNegated) {
331 toggleAttribute(attribs, StyleConstants.FontConstants.Bold);
332 }
333
334 return 1;
335 }
336
337 // Underline
338 if (string.charAt(0) == CODE_UNDERLINE) {
339 if (!isNegated) {
340 toggleAttribute(attribs, StyleConstants.FontConstants.Underline);
341 }
342
343 return 1;
344 }
345
346 // Italic
347 if (string.charAt(0) == CODE_ITALIC) {
348 if (!isNegated) {
349 toggleAttribute(attribs, StyleConstants.FontConstants.Italic);
350 }
351
352 return 1;
353 }
354
355 // Hyperlinks
356 if (string.charAt(0) == CODE_HYPERLINK) {
357 if (!isNegated) {
358 toggleLink(attribs);
359 }
360
361 if (attribs.getAttribute(IRCTextAttribute.HYPERLINK) == null) {
362 attribs.addAttribute(IRCTextAttribute.HYPERLINK,
363 readUntilControl(string.substring(1)));
364 } else {
365 attribs.removeAttribute(IRCTextAttribute.HYPERLINK);
366 }
367 return 1;
368 }
369
370 // Channel links
371 if (string.charAt(0) == CODE_CHANNEL) {
372 if (attribs.getAttribute(IRCTextAttribute.CHANNEL) == null) {
373 attribs.addAttribute(IRCTextAttribute.CHANNEL,
374 readUntilControl(string.substring(1)));
375 } else {
376 attribs.removeAttribute(IRCTextAttribute.CHANNEL);
377 }
378
379 return 1;
380 }
381
382 // Nickname links
383 if (string.charAt(0) == CODE_NICKNAME) {
384 if (attribs.getAttribute(IRCTextAttribute.NICKNAME) == null) {
385 attribs.addAttribute(IRCTextAttribute.NICKNAME,
386 readUntilControl(string.substring(1)));
387 } else {
388 attribs.removeAttribute(IRCTextAttribute.NICKNAME);
389 }
390
391 return 1;
392 }
393
394 // Fixed pitch
395 if (string.charAt(0) == CODE_FIXED) {
396 if (!isNegated) {
397 if (attribs.containsAttribute(StyleConstants.FontConstants.FontFamily, "monospaced")) {
398 attribs.removeAttribute(StyleConstants.FontConstants.FontFamily);
399 } else {
400 attribs.removeAttribute(StyleConstants.FontConstants.FontFamily);
401 attribs.addAttribute(StyleConstants.FontConstants.FontFamily, "monospaced");
402 }
403 }
404
405 return 1;
406 }
407
408 // Stop formatting
409 if (string.charAt(0) == CODE_STOP) {
410 if (!isNegated) {
411 resetAttributes(attribs);
412 }
413
414 return 1;
415 }
416
417 // Colours
418 if (string.charAt(0) == CODE_COLOUR) {
419 int count = 1;
420 // This isn't too nice!
421 if (string.length() > count && isInt(string.charAt(count))) {
422 int foreground = string.charAt(count) - '0';
423 count++;
424 if (string.length() > count && isInt(string.charAt(count))) {
425 foreground = foreground * 10 + (string.charAt(count) - '0');
426 count++;
427 }
428 foreground = foreground % 16;
429
430 if (!isNegated) {
431 setForeground(attribs, String.valueOf(foreground));
432 if (isStart) {
433 setDefaultForeground(attribs, String.valueOf(foreground));
434 }
435 }
436
437 // Now background
438 if (string.length() > count && string.charAt(count) == ','
439 && string.length() > count + 1
440 && isInt(string.charAt(count + 1))) {
441 int background = string.charAt(count + 1) - '0';
442 count += 2; // Comma and first digit
443 if (string.length() > count && isInt(string.charAt(count))) {
444 background = background * 10 + (string.charAt(count) - '0');
445 count++;
446 }
447 background = background % 16;
448
449 if (!isNegated) {
450 setBackground(attribs, String.valueOf(background));
451 if (isStart) {
452 setDefaultBackground(attribs, String.valueOf(background));
453 }
454 }
455 }
456 } else if (!isNegated) {
457 resetColour(attribs);
458 }
459 return count;
460 }
461
462 // Hex colours
463 if (string.charAt(0) == CODE_HEXCOLOUR) {
464 int count = 1;
465 if (hasHexString(string, 1)) {
466 if (!isNegated) {
467 setForeground(attribs, string.substring(1, 7).toUpperCase());
468 if (isStart) {
469 setDefaultForeground(attribs, string.substring(1, 7).toUpperCase());
470 }
471 }
472
473 count = count + 6;
474
475 // Now for background
476 if (string.charAt(count) == ',' && hasHexString(string, count + 1)) {
477 count++;
478
479 if (!isNegated) {
480 setBackground(attribs, string.substring(count, count + 6).toUpperCase());
481 if (isStart) {
482 setDefaultBackground(attribs,
483 string.substring(count, count + 6).toUpperCase());
484 }
485 }
486
487 count += 6;
488 }
489 } else if (!isNegated) {
490 resetColour(attribs);
491 }
492 return count;
493 }
494
495 // Control code negation
496 if (string.charAt(0) == CODE_NEGATE) {
497 toggleAttribute(attribs, "NegateControl");
498 return 1;
499 }
500
501 // Smilies!!
502 if (string.charAt(0) == CODE_SMILIE) {
503 if (attribs.getAttribute(IRCTextAttribute.SMILEY) == null) {
504 final String smilie = readUntilControl(string.substring(1));
505
506 attribs.addAttribute(IRCTextAttribute.SMILEY, "smilie-" + smilie);
507 } else {
508 attribs.removeAttribute(IRCTextAttribute.SMILEY);
509 }
510
511 return 1;
512 }
513
514 return 0;
515 }
516
517 /**
518 * Determines if the specified character represents a single integer (i.e. 0-9).
519 * @param c The character to check
520 * @return True iff the character is in the range [0-9], false otherwise
521 */
522 private static boolean isInt(final char c) {
/*
P/P * Method: bool isInt(char)
*
* Postconditions:
* init'ed(return_value)
*/
523 return c >= '0' && c <= '9';
524 }
525
526 /**
527 * Determines if the specified character represents a single hex digit
528 * (i.e., 0-F).
529 * @param c The character to check
530 * @return True iff the character is in the range [0-F], false otherwise
531 */
532 private static boolean isHex(final char c) {
/*
P/P * Method: bool isHex(char)
*
* Postconditions:
* init'ed(return_value)
*/
533 return isInt(c) || (c >= 'A' && c <= 'F');
534 }
535
536 /**
537 * Determines if the specified string has a 6-digit hex string starting at
538 * the specified offset.
539 * @param input The string to check
540 * @param offset The offset to start at
541 * @return True iff there is a hex string preset at the offset
542 */
543 private static boolean hasHexString(final String input, final int offset) {
544 // If the string's too short, it can't have a hex string
/*
P/P * Method: bool hasHexString(String, int)
*
* Preconditions:
* input != null
*
* Postconditions:
* init'ed(return_value)
*/
545 if (input.length() < offset + 6) {
546 return false;
547 }
548 boolean res = true;
549 for (int i = offset; i < 6 + offset; i++) {
550 res = res && isHex(input.toUpperCase(Locale.getDefault()).charAt(i));
551 }
552
553 return res;
554 }
555
556 /**
557 * Toggles the various hyperlink-related attributes.
558 * @param attribs The attributes to be modified.
559 */
560 private static void toggleLink(final SimpleAttributeSet attribs) {
/*
P/P * Method: void toggleLink(SimpleAttributeSet)
*
* Preconditions:
* init'ed(styleLinks)
* (soft) attribs != null
*
* Presumptions:
* init'ed(java.awt.Color.BLUE)
* init'ed(java.lang.Boolean.TRUE)
* init'ed(javax.swing.text.StyleConstants$FontConstants.Foreground)
* init'ed(javax.swing.text.StyleConstants$FontConstants.Underline)
*
* Test Vectors:
* styleLinks: {0}, {1}
* javax.swing.text.SimpleAttributeSet:containsAttribute(...)@565: {0}, {1}
* javax.swing.text.SimpleAttributeSet:containsAttribute(...)@583: {0}, {1}
* javax.swing.text.SimpleAttributeSet:getAttribute(...)@562: Inverse{null}, Addr_Set{null}
* javax.swing.text.SimpleAttributeSet:getAttribute(...)@571: Addr_Set{null}, Inverse{null}
* javax.swing.text.SimpleAttributeSet:getAttribute(...)@590: Addr_Set{null}, Inverse{null}
*/
561 if (styleLinks) {
562 if (attribs.getAttribute(IRCTextAttribute.HYPERLINK) == null) {
563 // Add the hyperlink style
564
565 if (attribs.containsAttribute(StyleConstants.FontConstants.Underline, Boolean.TRUE)) {
566 attribs.addAttribute("restoreUnderline", Boolean.TRUE);
567 } else {
568 attribs.addAttribute(StyleConstants.FontConstants.Underline, Boolean.TRUE);
569 }
570
571 final Object foreground = attribs.getAttribute(StyleConstants.FontConstants.Foreground);
572
573 if (foreground != null) {
574 attribs.addAttribute("restoreColour", foreground);
575 attribs.removeAttribute(StyleConstants.FontConstants.Foreground);
576 }
577
578 attribs.addAttribute(StyleConstants.FontConstants.Foreground, Color.BLUE);
579
580 } else {
581 // Remove the hyperlink style
582
583 if (attribs.containsAttribute("restoreUnderline", Boolean.TRUE)) {
584 attribs.removeAttribute("restoreUnderline");
585 } else {
586 attribs.removeAttribute(StyleConstants.FontConstants.Underline);
587 }
588
589 attribs.removeAttribute(StyleConstants.FontConstants.Foreground);
590 final Object foreground = attribs.getAttribute("restoreColour");
591 if (foreground != null) {
592 attribs.addAttribute(StyleConstants.FontConstants.Foreground, foreground);
593 attribs.removeAttribute("restoreColour");
594 }
595 }
596 }
597 }
598
599 /**
600 * Toggles the specified attribute. If the attribute exists in the attribute
601 * set, it is removed. Otherwise, it is added with a value of Boolean.True.
602 * @param attribs The attribute set to check
603 * @param attrib The attribute to toggle
604 */
605 private static void toggleAttribute(final SimpleAttributeSet attribs,
606 final Object attrib) {
/*
P/P * Method: void toggleAttribute(SimpleAttributeSet, Object)
*
* Preconditions:
* attribs != null
*
* Presumptions:
* init'ed(java.lang.Boolean.TRUE)
*
* Test Vectors:
* javax.swing.text.SimpleAttributeSet:containsAttribute(...)@607: {0}, {1}
*/
607 if (attribs.containsAttribute(attrib, Boolean.TRUE)) {
608 attribs.removeAttribute(attrib);
609 } else {
610 attribs.addAttribute(attrib, Boolean.TRUE);
611 }
612 }
613
614 /**
615 * Resets all attributes in the specified attribute list.
616 * @param attribs The attribute list whose attributes should be reset
617 */
618 private static void resetAttributes(final SimpleAttributeSet attribs) {
/*
P/P * Method: void resetAttributes(SimpleAttributeSet)
*
* Preconditions:
* attribs != null
*
* Presumptions:
* init'ed(java.lang.Boolean.TRUE)
* init'ed(javax.swing.text.StyleConstants$FontConstants.Bold)
* init'ed(javax.swing.text.StyleConstants$FontConstants.FontFamily)
* init'ed(javax.swing.text.StyleConstants$FontConstants.Italic)
* init'ed(javax.swing.text.StyleConstants$FontConstants.Underline)
*
* Test Vectors:
* javax.swing.text.SimpleAttributeSet:containsAttribute(...)@619: {0}, {1}
* javax.swing.text.SimpleAttributeSet:containsAttribute(...)@622: {0}, {1}
* javax.swing.text.SimpleAttributeSet:containsAttribute(...)@625: {0}, {1}
* javax.swing.text.SimpleAttributeSet:containsAttribute(...)@628: {0}, {1}
*/
619 if (attribs.containsAttribute(StyleConstants.FontConstants.Bold, Boolean.TRUE)) {
620 attribs.removeAttribute(StyleConstants.FontConstants.Bold);
621 }
622 if (attribs.containsAttribute(StyleConstants.FontConstants.Underline, Boolean.TRUE)) {
623 attribs.removeAttribute(StyleConstants.FontConstants.Underline);
624 }
625 if (attribs.containsAttribute(StyleConstants.FontConstants.Italic, Boolean.TRUE)) {
626 attribs.removeAttribute(StyleConstants.FontConstants.Italic);
627 }
628 if (attribs.containsAttribute(StyleConstants.FontConstants.FontFamily, "monospaced")) {
629 final Object defaultFont = attribs.getAttribute("DefaultFontFamily");
630 attribs.removeAttribute(StyleConstants.FontConstants.FontFamily);
631 attribs.addAttribute(StyleConstants.FontConstants.FontFamily, defaultFont);
632 }
633 resetColour(attribs);
634 }
635
636 /**
637 * Resets the colour attributes in the specified attribute set.
638 * @param attribs The attribute set whose colour attributes should be reset
639 */
640 private static void resetColour(final SimpleAttributeSet attribs) {
/*
P/P * Method: void resetColour(SimpleAttributeSet)
*
* Preconditions:
* attribs != null
*
* Presumptions:
* init'ed(javax.swing.text.StyleConstants.Background)
* init'ed(javax.swing.text.StyleConstants.Foreground)
*
* Test Vectors:
* javax.swing.text.SimpleAttributeSet:isDefined(...)@641: {0}, {1}
* javax.swing.text.SimpleAttributeSet:isDefined(...)@644: {0}, {1}
* javax.swing.text.SimpleAttributeSet:isDefined(...)@648: {0}, {1}
* javax.swing.text.SimpleAttributeSet:isDefined(...)@651: {0}, {1}
*/
641 if (attribs.isDefined(StyleConstants.Foreground)) {
642 attribs.removeAttribute(StyleConstants.Foreground);
643 }
644 if (attribs.isDefined("DefaultForeground")) {
645 attribs.addAttribute(StyleConstants.Foreground,
646 attribs.getAttribute("DefaultForeground"));
647 }
648 if (attribs.isDefined(StyleConstants.Background)) {
649 attribs.removeAttribute(StyleConstants.Background);
650 }
651 if (attribs.isDefined("DefaultBackground")) {
652 attribs.addAttribute(StyleConstants.Background,
653 attribs.getAttribute("DefaultBackground"));
654 }
655 }
656
657 /**
658 * Sets the foreground colour in the specified attribute set to the colour
659 * corresponding to the specified colour code or hex.
660 * @param attribs The attribute set to modify
661 * @param foreground The colour code/hex of the new foreground colour
662 */
663 private static void setForeground(final SimpleAttributeSet attribs,
664 final String foreground) {
/*
P/P * Method: void setForeground(SimpleAttributeSet, String)
*
* Preconditions:
* attribs != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS.length >= 1
* (soft) init'ed(com/dmdirc/ui/messages/ColourManager.IRC_COLOURS[...])
*
* Presumptions:
* init'ed(javax.swing.text.StyleConstants.Foreground)
*
* Test Vectors:
* javax.swing.text.SimpleAttributeSet:isDefined(...)@665: {0}, {1}
*/
665 if (attribs.isDefined(StyleConstants.Foreground)) {
666 attribs.removeAttribute(StyleConstants.Foreground);
667 }
668 attribs.addAttribute(StyleConstants.Foreground, ColourManager.parseColour(foreground));
669 }
670
671 /**
672 * Sets the background colour in the specified attribute set to the colour
673 * corresponding to the specified colour code or hex.
674 * @param attribs The attribute set to modify
675 * @param background The colour code/hex of the new background colour
676 */
677 private static void setBackground(final SimpleAttributeSet attribs,
678 final String background) {
/*
P/P * Method: void setBackground(SimpleAttributeSet, String)
*
* Preconditions:
* attribs != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS.length >= 1
* (soft) init'ed(com/dmdirc/ui/messages/ColourManager.IRC_COLOURS[...])
*
* Presumptions:
* init'ed(javax.swing.text.StyleConstants.Background)
*
* Test Vectors:
* javax.swing.text.SimpleAttributeSet:isDefined(...)@679: {0}, {1}
*/
679 if (attribs.isDefined(StyleConstants.Background)) {
680 attribs.removeAttribute(StyleConstants.Background);
681 }
682 attribs.addAttribute(StyleConstants.Background, ColourManager.parseColour(background));
683 }
684
685 /**
686 * Sets the default foreground colour (used after an empty ctrl+k or a ctrl+o).
687 * @param attribs The attribute set to apply this default on
688 * @param foreground The default foreground colour
689 */
690 private static void setDefaultForeground(final SimpleAttributeSet attribs,
691 final String foreground) {
/*
P/P * Method: void setDefaultForeground(SimpleAttributeSet, String)
*
* Preconditions:
* attribs != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS.length >= 1
* (soft) init'ed(com/dmdirc/ui/messages/ColourManager.IRC_COLOURS[...])
*/
692 attribs.addAttribute("DefaultForeground", ColourManager.parseColour(foreground));
693 }
694
695 /**
696 * Sets the default background colour (used after an empty ctrl+k or a ctrl+o).
697 * @param attribs The attribute set to apply this default on
698 * @param background The default background colour
699 */
700 private static void setDefaultBackground(final SimpleAttributeSet attribs,
701 final String background) {
/*
P/P * Method: void setDefaultBackground(SimpleAttributeSet, String)
*
* Preconditions:
* attribs != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS != null
* (soft) com/dmdirc/ui/messages/ColourManager.IRC_COLOURS.length >= 1
* (soft) init'ed(com/dmdirc/ui/messages/ColourManager.IRC_COLOURS[...])
*/
702 attribs.addAttribute("DefaultBackground", ColourManager.parseColour(background));
703 }
704
705 }
SofCheck Inspector Build Version : 2.17854
| Styliser.java |
2009-Jun-25 01:54:24 |
| Styliser.class |
2009-Sep-02 17:04:17 |
| Styliser$1.class |
2009-Sep-02 17:04:17 |