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 == &amp;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{&amp;"",&amp;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 == &amp;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