001    /*
002     Copyright (C) 2003 Adam Olsen
003     This program is free software; you can redistribute it and/or modify
004     it under the terms of the GNU General Public License as published by
005     the Free Software Foundation; either version 1, or (at your option)
006     any later version.
007     This program is distributed in the hope that it will be useful,
008     but WITHOUT ANY WARRANTY; without even the implied warranty of
009     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
010     GNU General Public License for more details.
011     You should have received a copy of the GNU General Public License
012     along with this program; if not, write to the Free Software
013     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
014     */
015    package com.valhalla.jbother;
016    
017    import java.awt.*;
018    import java.awt.event.MouseAdapter;
019    import java.awt.event.MouseEvent;
020    import java.io.BufferedReader;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.io.InputStreamReader;
024    import java.net.URL;
025    import java.util.*;
026    import java.util.regex.*;
027    
028    import javax.swing.*;
029    import javax.swing.text.*;
030    
031    import com.valhalla.gui.Standard;
032    import com.valhalla.settings.Settings;
033    
034    /**
035     * Replaces different emote symbols with images
036     *
037     * @author Adam Olsen
038     * @created October 30, 2004
039     * @version 1.0
040     */
041    public class ConversationFormatter {
042        private String imageDir;
043    
044        private static ConversationFormatter instance;
045    
046        private Properties map;
047    
048        private String themeDir;
049        Pattern url = Pattern.compile("(^|\\s)((ftp|https?)://[^\\s\"':]+?)(\\s|$)");
050        Pattern email = Pattern.compile("(\\s|^)((?!(ftp|http|https)://)[^\\s\"'\\(\\)\\[\\]><:]+?@[^<>\\s\"':]+?)(\\s|\\s?$)");
051        Pattern emoticon = Pattern.compile("<image_location=([^>]*)>");
052    
053        private InputStream emoticonStream;
054    
055        private boolean switched = false;
056    
057        protected ResourceBundle resources = ResourceBundle.getBundle("JBotherBundle", Locale.getDefault());
058        /**
059         * Default constructor - opens the emoticon theme dir and reads in the data
060         * file containing the different emote definitions
061         */
062        private ConversationFormatter() {
063        }
064    
065        /**
066         *
067         *
068         * @return the ConversationFormatter singleton
069         */
070        public static ConversationFormatter getInstance() {
071            if (instance == null) {
072                instance = new ConversationFormatter();
073            }
074            if (!instance.switched) {
075                instance.switchTheme(Settings.getInstance().getProperty(
076                        "emoticonTheme"));
077            }
078            return instance;
079        }
080    
081        /**
082         * Switches the emoticon theme
083         *
084         * @param theme
085         *            the theme to switch to
086         */
087        public void switchTheme(String theme) {
088            switched = true;
089    
090            map = new Properties();
091    
092            themeDir = theme;
093            if (themeDir == null) {
094                themeDir = "default";
095            }
096    
097            emoticonStream = getClass().getClassLoader().getResourceAsStream(
098                    "imagethemes/emoticons/" + themeDir + '/' + "index.dat");
099    
100            if (emoticonStream == null) {
101                com.valhalla.Logger.debug("Bad Emoticon File");
102                return;
103            }
104    
105            InputStreamReader in = new InputStreamReader(emoticonStream);
106            BufferedReader reader = new BufferedReader(in);
107    
108            String line;
109            String nameValue[] = new String[2];
110    
111            try {
112                map.setProperty("(-)(-)", "fairy.gif");
113                map.setProperty(":birdie:", "funny_bird.gif");
114                while ((line = reader.readLine()) != null) {
115                    nameValue = line.split(" ");
116                    if (nameValue[0] == null || nameValue[1] == null) {
117                        break;
118                    }
119    
120                    nameValue[0] = nameValue[0].replaceAll("<", "&lt;");
121                    nameValue[0] = nameValue[0].replaceAll(">", "&gt;");
122                    map.setProperty(nameValue[0], nameValue[1]);
123                }
124    
125                in.close();
126                // close the file
127            } catch (IOException e) {
128                com.valhalla.Logger.debug("Couldn't read emoticon file.");
129            }
130        }
131    
132        /**
133         * Replaces the different symbols with the images defined in the emote data
134         * file
135         *
136         * @param text
137         *            the text to modify
138         * @return the modified text
139         */
140        public void replaceIcons(String text, StyledDocument doc, SimpleAttributeSet sas, JTextPane pane, boolean emotes) {
141            if( emotes ) {
142                if (emoticonStream == null) {
143                    return;
144                }
145    
146                Iterator iterator = map.keySet().iterator();
147                String symbol, image, imageLocation;
148    
149                while (iterator.hasNext()) {
150                    symbol = (String) iterator.next();
151    
152                    image = map.getProperty(symbol);
153                    imageLocation = "imagethemes/emoticons/" + themeDir + "/" + image;
154                    if (symbol.equals("(-)(-)") || symbol.equals(":birdie:")) {
155                        imageLocation = "imagethemes/emoticons/" + image;
156                    }
157    
158                    StringBuffer newText = null;
159                    int l1 = 0, l2 = 0;
160    
161                    while ((l2 = text.indexOf(symbol, l1)) != -1) {
162                        if (newText == null)
163                            newText = new StringBuffer(text.length() + 40);
164    
165                        String before = text.substring(l1, l2);
166                        newText.append(before);
167    
168                        int after = l2 + symbol.length();
169                        if ((l1 == l2 ||
170                                before.endsWith(" ")) &&
171                                (after == text.length() - 1 ||
172                                text.startsWith(" ", after))) {
173                            newText.append("<image_location=" + imageLocation + ">");
174                        } else {
175                            newText.append(symbol);
176                        }
177                        l1 = after;
178                    }
179    
180                    if (newText != null) {
181                        if (l1 < text.length())
182                            newText.append(text.substring(l1));
183                        text = newText.toString();
184                    }
185                }
186    
187                int b = -1;
188                while(true) {
189                    Matcher m = emoticon.matcher(text);
190                    if( !m.find() ) break;
191    
192                    String i = m.group(1);
193                    b = text.indexOf("<image_location=" + i + ">");
194                    if(b == -1) break;
195                    String before = text.substring(0, b);
196                    before = checkHyperlink(before, doc, sas);
197    
198                    ImageIcon icon = Standard.getIcon(i);
199                    try {
200                        doc.insertString(doc.getLength(), before, sas);
201                        SimpleAttributeSet set = new SimpleAttributeSet();
202                        StyleConstants.setIcon(set, icon);
203                        doc.insertString(doc.getLength(), "o", set);
204    
205                    } catch( Exception e ) { }
206    
207                    text = text.substring(b + ("<image_location=" + i + ">").length());
208    
209                }
210    
211                text = checkHyperlink(text, doc, sas);
212            }
213    
214            try {
215                doc.insertString(doc.getLength(), text, sas);
216            } catch( Exception e){}
217        }
218    
219        public String checkHyperlink(String text, StyledDocument doc, SimpleAttributeSet sas) {
220            if(StyleConstants.getForeground(sas) != Color.BLACK) return text;
221    
222            Color link = new Color(91,88,188);
223    
224            while(true) {
225                Matcher m = url.matcher(text.replaceAll("\n", " "));
226                if(!m.find()) break;
227    
228                String address = m.group(2);
229    
230                int b = text.indexOf(address);
231                if(b == -1) break;
232                String before = text.substring(0, b);// + m.group(1);
233    
234                SimpleAttributeSet newSas = (SimpleAttributeSet)sas.clone();
235                StyleConstants.setUnderline(newSas, true);
236                Color test = (Color)sas.getAttribute(StyleConstants.Foreground);
237                if( test != null && test == Color.BLACK ) StyleConstants.setForeground(newSas, link);
238                try {
239                    doc.insertString(doc.getLength(), before, sas);
240                    doc.insertString(doc.getLength(), address, newSas);
241                    //doc.insertString(doc.getLength(), m.group(4), sas);
242                } catch( Exception e ) { }
243    
244                text = text.substring(b + address.length());
245            }
246    
247            while(true) {
248                Matcher m = email.matcher(text.replaceAll("\n", " "));
249                if(!m.find()) break;
250    
251                String address = m.group(2);
252    
253                int b = text.indexOf(address);
254                if(b == -1) break;
255                String before = text.substring(0, b);// + m.group(1);
256    
257                SimpleAttributeSet newSas = (SimpleAttributeSet)sas.clone();
258                StyleConstants.setUnderline(newSas, true);
259    
260                Color test = (Color)sas.getAttribute(StyleConstants.Foreground);
261                if( test != null && test == Color.BLACK ) StyleConstants.setForeground(newSas, link);
262    
263    
264                try {
265                    doc.insertString(doc.getLength(), before, sas);
266                    doc.insertString(doc.getLength(), address, newSas);
267                    //doc.insertString(doc.getLength(), m.group(4), sas);
268                } catch( Exception e ) { }
269    
270                text = text.substring(b + address.length());
271            }
272    
273            return text;
274        }
275    
276        /**
277         * Shows a small window displaying all available emoticons.
278         *
279         * @param window
280         *            the parent window
281         * @param component
282         *            the Component to display this frame over
283         * @param area
284         *            the text area that the emoticons will be placed on when the
285         *            user clicks an image
286         */
287        public void displayEmoticonChooser(JFrame window, Component component,
288                JTextComponent area) {
289            if(themeDir.equals("no emoticons"))
290            {
291                Standard.warningMessage(null,"No Emoticons", "No emoticons to display.");
292                return;
293            }
294            JDialog dialog = new JDialog(window);
295            JPanel panel = (JPanel) dialog.getContentPane();
296            panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
297            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
298            EmoteClickListener listener = new EmoteClickListener(dialog, area);
299    
300            int columns = 6;
301            int current = 0;
302            JPanel cPanel = null;
303    
304            Iterator i = map.keySet().iterator();
305    
306            Properties displayed = new Properties();
307            while (i.hasNext()) {
308                String symbol = (String) i.next();
309    
310                if (symbol.equals("(-)(-)") || symbol.equals(":birdie:")) {
311                    continue;
312                }
313    
314                String image = map.getProperty(symbol);
315    
316                if (displayed.getProperty(image) != null)
317                    continue;
318                displayed.setProperty(image, "True");
319    
320                String imageLocation = "imagethemes/emoticons/" + themeDir + "/"
321                        + image;
322                ImageIcon icon = Standard.getIcon(imageLocation);
323                if (icon == null) {
324                    continue;
325                }
326    
327                if (current > columns) {
328                    panel.add(cPanel);
329                    current = 0;
330                }
331    
332                if (current == 0) {
333                    cPanel = new JPanel();
334                    cPanel.setLayout(new BoxLayout(cPanel, BoxLayout.X_AXIS));
335                }
336    
337                JLabel label = new JLabel(icon);
338                label.setName(symbol);
339                label.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
340                label.addMouseListener(listener);
341                if (icon != null) {
342                    cPanel.add(label);
343                }
344                current++;
345            }
346    
347            dialog.pack();
348            dialog.setLocationRelativeTo(component);
349            dialog.setVisible(true);
350            dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
351        }
352    
353        /**
354         * Listens for an emoticon to get clicked
355         *
356         * @author synic
357         * @created October 30, 2004
358         */
359        class EmoteClickListener extends MouseAdapter {
360            JDialog dialog;
361    
362            JTextComponent area;
363    
364            /**
365             * Constructor for the EmoteClickListener object
366             *
367             * @param dialog
368             *            the emote dialog that called this listener
369             * @param area
370             *            the textarea to append the emote symbol to
371             */
372            public EmoteClickListener(JDialog dialog, JTextComponent area) {
373                this.dialog = dialog;
374                this.area = area;
375            }
376    
377            /**
378             * Called by the mouse listener
379             *
380             * @param e
381             *            the mouse event
382             */
383            public void mouseReleased(MouseEvent e) {
384                dialog.dispose();
385                JLabel label = (JLabel) e.getSource();
386                String symbol = label.getName();
387                area.setText(area.getText()
388                + symbol.replaceAll("&gt;", ">").replaceAll("&lt;", "<")
389                + " ");
390                area.grabFocus();
391            }
392        }
393    }
394