1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.xnap.plugin.festival;
21
22 import java.awt.GridBagLayout;
23 import java.io.IOException;
24 import java.util.List;
25 import java.beans.*;
26
27 import javax.swing.JTextField;
28
29 import org.apache.log4j.Logger;
30 import org.xnap.XNap;
31 import org.xnap.XNapFacade;
32 import org.xnap.chat.Channel;
33 import org.xnap.chat.ChannelEvent;
34 import org.xnap.chat.ChannelListener;
35 import org.xnap.chat.ChatManager;
36 import org.xnap.event.ListEvent;
37 import org.xnap.event.ListListener;
38 import org.xnap.gui.*;
39 import org.xnap.gui.util.*;
40 import org.xnap.gui.component.FilePanel;
41 import org.xnap.gui.event.AbstractPreferencesDialogListener;
42 import org.xnap.gui.util.GridBagHelper;
43 import org.xnap.plugin.AbstractPlugin;
44 import org.xnap.plugin.PluginInitializeException;
45 import org.xnap.util.AbstractPluginPreferences;
46 import org.xnap.util.StringHelper;
47
48 /***
49 * Provides a simple text-to-speech plugin using festival.
50 *
51 * For now it reads incoming chat messages aloud.
52 */
53 public class FestivalPlugin extends AbstractPlugin
54 implements ListListener, ChannelListener, PropertyChangeListener
55 {
56
57
58
59 public static String ICON_FILENAME = "festival.png";
60
61
62
63 /***
64 * Remembers if there was an exception starting festival.
65 */
66 private boolean failed = false;
67 private Process festivalProcess = null;
68 private FestivalRunner runner = null;
69 private FestivalMessageQueue queue;
70 private FestivalPreferences festivalPrefs;
71 private static Logger logger = Logger.getLogger(FestivalPlugin.class);
72 private PreferencesDialogListener pdl;
73
74
75
76 public FestivalPlugin()
77 {
78
79 }
80
81
82
83 /***
84 * @see xnap.plugin.Plugin#start()
85 */
86 public void start() throws Exception
87 {
88 festivalPrefs = new FestivalPreferences();
89
90 queue = new FestivalMessageQueue();
91
92
93 try {
94 startFestivalProcess();
95 }
96 catch (IOException ie) {
97
98
99 }
100
101
102 Channel[] channels = ChatManager.getInstance().getChannels();
103
104 for (int i = 0; i < channels.length; i++) {
105 channels[i].addChannelListener(this);
106 }
107
108
109 ChatManager.getInstance().addChannelListListener(this);
110
111 festivalPrefs.addPropertyChangeListener("command", this);
112 }
113
114 private void startFestivalProcess() throws IOException
115 {
116 festivalProcess = Runtime.getRuntime().exec(festivalPrefs.getCommand());
117 runner = new FestivalRunner(queue, festivalProcess.getOutputStream());
118 runner.start();
119 }
120
121 private void stopFestivalProcess()
122 {
123 if (runner != null) {
124 runner.stop();
125 runner = null;
126 }
127 if (festivalProcess != null) {
128 festivalProcess.destroy();
129 festivalProcess = null;
130 }
131 }
132
133 private void tts(String text)
134 {
135 text = StringHelper.replaceAll(text, "'\"", "");
136 text = "(SayText \"" + text + "\")\n";
137 queue.addMessage(text);
138 }
139
140 public String getName()
141 {
142 return getInfo().getName();
143 }
144
145 /***
146 * @see xnap.plugin.Plugin#startGUI()
147 */
148 public void startGUI()
149 {
150 pdl = new PreferencesDialogListener();
151 XNapFacade.addPluginPreferencesDialogListener(pdl);
152 }
153
154 /***
155 * @see xnap.plugin.Plugin#stop()
156 */
157 public void stop()
158 {
159 ChatManager.getInstance().removeChannelListListener(this);
160
161 Channel[] channels = ChatManager.getInstance().getChannels();
162
163 for (int i = 0; i < channels.length; i++) {
164 channels[i].removeChannelListener(this);
165 }
166 queue.clear();
167 queue = null;
168 stopFestivalProcess();
169
170 festivalPrefs.removePropertyChangeListener("command", this);
171 festivalPrefs = null;
172 }
173
174 /***
175 * @see xnap.plugin.Plugin#stopGUI()
176 */
177 public void stopGUI()
178 {
179 pdl.dispose();
180 XNapFacade.removePluginPreferencesDialogListener(pdl);
181 pdl = null;
182 }
183
184 /***
185 * @see xnap.event.ListListener#itemAdded(xnap.event.ListEvent)
186 */
187 public void itemAdded(ListEvent event)
188 {
189 ((Channel)event.getItem()).addChannelListener(this);
190 }
191
192 /***
193 * @see xnap.event.ListListener#itemRemoved(xnap.event.ListEvent)
194 */
195 public void itemRemoved(ListEvent event)
196 {
197 ((Channel)event.getItem()).removeChannelListener(this);
198 }
199
200 /***
201 * @see xnap.chat.ChannelListener#channelJoined(xnap.chat.ChannelEvent)
202 */
203 public void channelJoined(ChannelEvent event)
204 {
205 }
206
207 /***
208 * @see xnap.chat.ChannelListener#channelParted(xnap.chat.ChannelEvent)
209 */
210 public void channelParted(ChannelEvent event)
211 {
212 }
213
214 /***
215 * @see xnap.chat.ChannelListener#messageReceived(xnap.chat.ChannelEvent)
216 */
217 public void messageReceived(ChannelEvent event)
218 {
219 if (!((Channel)event.getSource()).isLocal(event.getPeer())) {
220 tts(event.getMessage());
221 }
222 }
223
224 /***
225 * @see xnap.chat.ChannelListener#peerAdded(xnap.chat.ChannelEvent)
226 */
227 public void peerAdded(ChannelEvent event)
228 {
229 }
230
231 /***
232 * @see xnap.chat.ChannelListener#peerChanged(xnap.chat.ChannelEvent)
233 */
234 public void peerChanged(ChannelEvent event)
235 {
236
237 }
238
239 /***
240 * @see xnap.chat.ChannelListener#peerRemoved(xnap.chat.ChannelEvent)
241 */
242 public void peerRemoved(ChannelEvent event)
243 {
244
245 }
246
247 /***
248 * @see xnap.chat.ChannelListener#topicChanged(xnap.chat.ChannelEvent)
249 */
250 public void topicChanged(ChannelEvent event)
251 {
252
253 }
254
255 public void propertyChange(PropertyChangeEvent e)
256 {
257 stopFestivalProcess();
258 try {
259 startFestivalProcess();
260 }
261 catch (IOException ie) {
262
263 logger.debug("Could not start festival process", ie);
264 }
265 }
266
267
268
269 private static class FestivalPreferences extends AbstractPluginPreferences {
270
271 public static final int VERSION = 1;
272
273 public FestivalPreferences()
274 {
275 super("plugin.festival", VERSION);
276
277 setDefault("command", "festival");
278 }
279
280 public String getCommand() { return get("command"); }
281 public void setCommand(String command) { set("command", command); }
282
283 }
284
285 private class PreferencesDialogListener
286 extends AbstractPreferencesDialogListener
287 {
288
289 public void addPanels(AbstractPreferencesDialog dialog, List panels)
290 {
291 FestivalPreferencesPanel jpp = new FestivalPreferencesPanel();
292 panels.add(jpp);
293 panels.add(dialog.addPanel(jpp, ICON_FILENAME));
294 }
295
296 }
297
298 private class FestivalPreferencesPanel extends AbstractSettingsPanel {
299
300
301
302 private FilePanel commandTextField;
303
304
305
306 public FestivalPreferencesPanel()
307 {
308 setLayout(new GridBagLayout());
309
310
311 commandTextField = new FilePanel(festivalPrefs.getCommand(), 20);
312 GridBagHelper.addLabel(this, XNap.tr("Command")).setLabelFor
313 (commandTextField.getTextField());
314 GridBagHelper.add(this, commandTextField, false);
315
316 GridBagHelper.addVerticalSpacer(this);
317 GUIHelper.setMnemonics(this);
318 }
319
320
321
322 public void apply()
323 {
324 festivalPrefs.setCommand(commandTextField.getFilename());
325 }
326
327 public String getDescription()
328 {
329 return XNap.tr("Command configuration.");
330 }
331
332 public String getTitle()
333 {
334 return FestivalPlugin.this.getInfo().getName();
335 }
336
337 }
338
339 }