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("<", "<");
121 nameValue[0] = nameValue[0].replaceAll(">", ">");
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(">", ">").replaceAll("<", "<")
389 + " ");
390 area.grabFocus();
391 }
392 }
393 }
394