View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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  	//--- Constant(s) ---
58  
59      public static String ICON_FILENAME = "festival.png";
60  	
61  	//--- Data field(s) ---
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  	//--- Constructor(s) ---
75  	
76      public FestivalPlugin()
77  	{
78  		
79  	}
80  
81  	//--- Method(s) ---
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  		// start festival process first in case there is an exception
93  		try {
94  			startFestivalProcess();
95  		}
96  		catch (IOException ie) {
97  			// throw new PluginInitializeException
98  // 				(XNap.tr("Could not start Festival."));
99  		}
100 
101 		// register existing channels
102 		Channel[] channels = ChatManager.getInstance().getChannels();
103 
104 		for (int i = 0; i < channels.length; i++) {
105 			channels[i].addChannelListener(this);
106 		}
107 
108 		// register list listener to get notification of new channels
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 			// maybe show this to the user
263 			logger.debug("Could not start festival process", ie);
264 		}
265 	}
266 
267     // --- Inner Class(es) ---
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 		//--- Data field(s) ---
301 
302 		private FilePanel commandTextField;
303 
304 		//--- Constructor(s) ---
305 		
306 		public FestivalPreferencesPanel()
307 		{
308 			setLayout(new GridBagLayout());
309 			
310 			// command name
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 		//--- Method(s) ---
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 }