001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.network;
020
021import icy.util.StringUtil;
022
023import java.awt.Color;
024
025import javax.swing.text.BadLocationException;
026import javax.swing.text.Document;
027import javax.swing.text.SimpleAttributeSet;
028import javax.swing.text.StyleConstants;
029
030/**
031 * IRC utilities class.
032 * 
033 * @author Stephane
034 */
035public class IRCUtil
036{
037    public static final char CHAR_BOLD = 2;
038    public static final char CHAR_ITALIC = 22;
039    public static final char CHAR_UNDERLINE = 31;
040    public static final char CHAR_COLOR = 3;
041    public static final char CHAR_RESET = 15;
042
043    public static class IRCAttribute
044    {
045        final static int UNKNOWN = -1;
046        final static int NORMAL = 0;
047        final static int BOLD = 1;
048        final static int ITALIC = 2;
049        final static int UNDERLINE = 4;
050
051        final static int COLOR = 9;
052
053        int type;
054        int size;
055        int arg1;
056        int arg2;
057
058        public IRCAttribute()
059        {
060            super();
061
062            type = NORMAL;
063            size = 1;
064            arg1 = -1;
065            arg2 = -1;
066        }
067    }
068
069    public static class IRCAttributeSet
070    {
071        int style;
072        Color foreground;
073        Color background;
074
075        public IRCAttributeSet()
076        {
077            super();
078
079            // default
080            style = IRCAttribute.NORMAL;
081            foreground = null;
082            background = null;
083        }
084    }
085
086    /**
087     * Insert the specified IRC string into specified Document.
088     * 
089     * @param ircString
090     *        IRC string containing IRC code.
091     * @param doc
092     *        doc when we want to insert the IRC styled string.
093     * @param defaultAttributes
094     *        default string attributes.
095     * @throws BadLocationException
096     */
097    public static void insertString(String ircString, Document doc, SimpleAttributeSet defaultAttributes)
098            throws BadLocationException
099    {
100        final IRCAttributeSet state = new IRCAttributeSet();
101        SimpleAttributeSet attr = new SimpleAttributeSet(defaultAttributes);
102
103        final int len = ircString.length();
104        int curInd = 0;
105
106        while (curInd < len)
107        {
108            int ctrlIndex = StringUtil.getNextCtrlCharIndex(ircString, curInd);
109            // end of line
110            if (ctrlIndex == -1)
111                ctrlIndex = len;
112
113            if (curInd != ctrlIndex)
114                // insert styled string
115                doc.insertString(doc.getLength(), ircString.substring(curInd, ctrlIndex), attr);
116
117            // end of string --> terminate
118            if (ctrlIndex >= len)
119                break;
120
121            // get IRC attribute
122            final IRCAttribute ircAttr = getAttribute(ircString, ctrlIndex);
123
124            // not an IRC attribute --> insert control character in document
125            if (ircAttr.type == IRCAttribute.UNKNOWN)
126                doc.insertString(doc.getLength(), ircString.substring(ctrlIndex, ctrlIndex + 1), attr);
127            else
128            {
129                // apply attribute to current state
130                applyAttribute(state, ircAttr);
131                // create attributes from default attributes and current IRC attribute state
132                attr = createAttributeSet(defaultAttributes, state);
133            }
134
135            // next...
136            curInd = ctrlIndex + ircAttr.size;
137        }
138    }
139
140    /**
141     * Return the IRC attribute corresponding to the control code at specified index.
142     */
143    public static IRCAttribute getAttribute(String ircString, int index)
144    {
145        final IRCAttribute result = new IRCAttribute();
146        final int len = ircString.length();
147
148        // no more text
149        if (index >= len)
150            return null;
151
152        int offset = index + 1;
153        int end;
154
155        switch (ircString.charAt(index))
156        {
157            case CHAR_RESET:
158                // reset default
159                result.type = IRCAttribute.NORMAL;
160                break;
161
162            case CHAR_BOLD:
163                // switch bold
164                result.type = IRCAttribute.BOLD;
165                break;
166
167            case CHAR_ITALIC:
168                // switch italic
169                result.type = IRCAttribute.ITALIC;
170                break;
171
172            case CHAR_UNDERLINE:
173                // switch underline
174                result.type = IRCAttribute.UNDERLINE;
175                break;
176
177            case CHAR_COLOR:
178                // color
179                end = StringUtil.getNextNonDigitCharIndex(ircString, offset);
180                // no more than 2 digits to encode color
181                if ((end == -1) || (end > (offset + 2)))
182                    end = Math.min(len, offset + 2);
183
184                // color info ?
185                if (end != offset)
186                {
187                    // get foreground color
188                    result.arg1 = Integer.parseInt(ircString.substring(offset, end));
189
190                    // update position
191                    offset = end;
192
193                    // search if we have background color
194                    if ((offset < len) && (ircString.charAt(offset) == ','))
195                    {
196                        offset++;
197
198                        end = StringUtil.getNextNonDigitCharIndex(ircString, offset);
199                        // no more than 2 digits to encode color
200                        if ((end == -1) || (end > (offset + 2)))
201                            end = Math.min(len, offset + 2);
202
203                        // get background color
204                        if (end != offset)
205                            result.arg2 = Integer.parseInt(ircString.substring(offset, end));
206                    }
207                }
208
209                result.size = end - index;
210                break;
211
212            default:
213                // unknown
214                result.type = IRCAttribute.UNKNOWN;
215                // System.out.println("code " + Integer.toString(ircString.charAt(index)));
216                break;
217        }
218
219        return result;
220    }
221
222    /**
223     * Apply the specified IRC attribute on specified IRC attributes set.<br>
224     * Some IRC attribute work as switch :<br>
225     * If bold is already set and you apply bold again, then bold is removed from set.
226     * 
227     * @param set
228     *        IRC attributes set.
229     * @param attr
230     *        IRC single attribute.
231     */
232    public static void applyAttribute(IRCAttributeSet set, IRCAttribute attr)
233    {
234        switch (attr.type)
235        {
236            case IRCAttribute.NORMAL:
237                // reset attribute
238                set.style = IRCAttribute.NORMAL;
239                set.foreground = null;
240                set.background = null;
241                break;
242
243            case IRCAttribute.BOLD:
244            case IRCAttribute.ITALIC:
245            case IRCAttribute.UNDERLINE:
246                // switch-able attribute
247                set.style ^= attr.type;
248                break;
249
250            case IRCAttribute.COLOR:
251                // no color information
252                if (attr.arg1 == -1)
253                {
254                    // reset
255                    set.foreground = null;
256                    set.background = null;
257                }
258                else
259                {
260                    set.foreground = getIRCColor(attr.arg1);
261                    // background color info ?
262                    if (attr.arg2 != -1)
263                        set.background = getIRCColor(attr.arg2);
264                }
265                break;
266        }
267    }
268
269    /**
270     * Return a new AttributeSet from the given default set and IRC attributes.
271     * 
272     * @param defaultAttributes
273     *        default Attribute Set we use as base attributes.
274     * @param ircAttributes
275     *        IRC attributes used to modifying default attributes.
276     */
277    public static SimpleAttributeSet createAttributeSet(SimpleAttributeSet defaultAttributes,
278            IRCAttributeSet ircAttributes)
279    {
280        final SimpleAttributeSet result = new SimpleAttributeSet(defaultAttributes);
281
282        if ((ircAttributes.style & IRCAttribute.BOLD) != 0)
283            StyleConstants.setBold(result, true);
284        if ((ircAttributes.style & IRCAttribute.ITALIC) != 0)
285            StyleConstants.setItalic(result, true);
286        if ((ircAttributes.style & IRCAttribute.UNDERLINE) != 0)
287            StyleConstants.setUnderline(result, true);
288
289        if (ircAttributes.foreground != null)
290            StyleConstants.setForeground(result, ircAttributes.foreground);
291        if (ircAttributes.background != null)
292            StyleConstants.setBackground(result, ircAttributes.background);
293
294        return result;
295    }
296
297    /**
298     * Return the color corresponding to the specified IRC color code.
299     */
300    public static Color getIRCColor(int num)
301    {
302        switch (num)
303        {
304            case 0:
305            case 16:
306                // white
307                return Color.white;
308            case 1:
309                return Color.black;
310            case 2:
311                return Color.blue;
312            case 3:
313                return Color.green;
314            case 4:
315                return Color.red;
316            case 5:
317                // brown
318                return new Color(0x8b4513);
319            case 6:
320                // purple
321                return new Color(0xa020f0);
322            case 7:
323                return Color.orange;
324            case 8:
325                return Color.yellow;
326            case 9:
327                // light green
328                return new Color(0x80ff00);
329            case 10:
330                return Color.cyan;
331            case 11:
332                // light cyan
333                return new Color(0x80ffff);
334            case 12:
335                // light blue
336                return new Color(0x8080ff);
337            case 13:
338                return Color.pink;
339            case 14:
340                return Color.darkGray;
341            case 15:
342                return Color.lightGray;
343            default:
344                // transparent
345                return new Color(0, true);
346        }
347    }
348
349    /**
350     * Returns IRC bold version of specified string.
351     */
352    public static String getBoldString(String value)
353    {
354        return CHAR_BOLD + value + CHAR_BOLD;
355    }
356
357    /**
358     * Returns IRC italic version of specified string.
359     */
360    public static String getItalicString(String value)
361    {
362        return CHAR_ITALIC + value + CHAR_ITALIC;
363    }
364
365    /**
366     * Returns IRC underline version of specified string.
367     */
368    public static String getUnderlineString(String value)
369    {
370        return CHAR_UNDERLINE + value + CHAR_UNDERLINE;
371    }
372
373}