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.gui;
21  
22  import java.awt.BorderLayout;
23  import java.awt.Toolkit;
24  import java.awt.event.ActionEvent;
25  import java.awt.event.ActionListener;
26  import java.awt.event.MouseListener;
27  import java.beans.PropertyChangeEvent;
28  import java.beans.PropertyChangeListener;
29  import java.util.StringTokenizer;
30  import javax.swing.AbstractAction;
31  import javax.swing.*;
32  import javax.swing.table.*;
33  import javax.swing.BoxLayout;
34  import javax.swing.JButton;
35  import javax.swing.JCheckBox;
36  import javax.swing.JLabel;
37  import javax.swing.JMenu;
38  import javax.swing.JMenuItem;
39  import javax.swing.JOptionPane;
40  import javax.swing.JPanel;
41  import javax.swing.JPopupMenu;
42  import javax.swing.JScrollPane;
43  import javax.swing.JSplitPane;
44  import javax.swing.*;
45  import javax.swing.SwingUtilities;
46  import javax.swing.border.EmptyBorder;
47  import org.xnap.XNap;
48  import org.xnap.chat.Channel;
49  import org.xnap.chat.ChannelEvent;
50  import org.xnap.chat.ChannelListener;
51  import org.xnap.cmdl.Command;
52  import org.xnap.cmdl.Console;
53  import org.xnap.cmdl.Executer;
54  import org.xnap.cmdl.LocalExecuter;
55  import org.xnap.gui.action.MenuAction;
56  import org.xnap.gui.component.*;
57  import org.xnap.gui.component.XNapTextField;
58  import org.xnap.gui.component.XNapToggleButton;
59  import org.xnap.gui.event.PopupListener;
60  import org.xnap.gui.menu.PeerMenu;
61  import org.xnap.gui.table.ChatPeerTableModel;
62  import org.xnap.gui.util.FocusHandler;
63  import org.xnap.gui.util.GUIHelper;
64  import org.xnap.peer.Peer;
65  import org.xnap.util.Formatter;
66  import org.xnap.util.Preferences;
67  
68  /***
69   * Provides a panel that handles a chat {@link Channel}. Provides a 
70   * {@link ChatPane} that displays the messages and a table that 
71   * contains the joined {@link Peer} objects.
72   */
73  public class ChannelPanel extends JPanel 
74      implements ChannelListener, PropertyChangeListener, PeerProvider, 
75  			   ActionProvider, Console {
76  
77      // --- Data Field(s) ---
78  
79      private Preferences prefs = Preferences.getInstance();
80  
81  	private ChatPanel parent;
82      
83      private ChatPane cpChat;
84      private JTable jta;
85      private XNapTextField jteInput;
86      private ChatPeerTableModel ptm;
87      private JSplitPane jspH;
88      private JLabel jlTopic;
89      private JLabel jlServer;
90      private JCheckBox jcbShowChatMsgTime;
91      private JCheckBox jcbBeepOnChatMessage;
92      private JCheckBox jcbBlinkOnChatMessage;
93      private SendAction acSend = new SendAction();
94  
95      private Channel channel;
96  	
97  	private LocalExecuter executer;
98  
99      //--- Constructor(s) ---
100 		
101     public ChannelPanel(ChatPanel parent, Channel channel)
102     {
103 		this.parent = parent;
104 		this.channel = channel;
105 
106 		initialize();
107 
108 		// FIX: the following two statements should be atomic
109 		Peer[] peers = channel.getPeers();
110 		channel.addChannelListener(this);
111 		for (int i = 0; i < peers.length; i++) {
112 			ptm.add(peers[i]);
113 		}
114 		updateStatus();
115 
116 		if (prefs.getPrintServerNotificationsInChatWindow() 
117 			&& channel.getProvider() != null) {
118 			println(XNap.tr("connected to {0}", 
119 							channel.getProvider().getName()), 
120 					"chatInfo");
121 		}
122 
123 		Command[] commands = channel.getCommands();
124 		if (commands != null) {
125 			executer = new LocalExecuter(commands);
126 		}		
127 
128 		jspH.addPropertyChangeListener
129 			(JSplitPane.DIVIDER_LOCATION_PROPERTY,
130 			 this);
131     }
132 
133     // --- Method(s) ---
134 
135     private void initialize()
136     {
137 		cpChat = new ChatPane();
138 
139 		Box boxTop = new Box(BoxLayout.X_AXIS);
140 
141 		// topic
142 		boxTop.add(Box.createHorizontalStrut(5));
143 		jlTopic = new SqueezedTextLabel();
144 		setTopic(channel.getTopic());
145 		boxTop.add(jlTopic);
146 
147 		boxTop.add(Box.createGlue());
148 
149 		jcbShowChatMsgTime = new JCheckBox
150 			(XNap.tr("Timestamp Messages"), prefs.getShowChatMsgTime());
151 		boxTop.add(jcbShowChatMsgTime);
152 
153 		jcbBeepOnChatMessage = new JCheckBox
154 			(XNap.tr("Beep"), prefs.getBeepOnChatMessage());
155 		boxTop.add(jcbBeepOnChatMessage);
156 
157 		jcbBlinkOnChatMessage = new JCheckBox(XNap.tr("Blink"), false);
158 		boxTop.add(jcbBlinkOnChatMessage);
159 
160 		// peers
161 		JPanel jpPeers = new JPanel(new BorderLayout());
162 
163 		JMenu jm = new ChatPeerMenu(this);
164 	    
165 		ptm = new ChatPeerTableModel(getChannel());
166 		jta = ptm.createTable(prefs, "user");
167 		MouseListener ml = new PopupListener(jm);
168 		jta.addMouseListener(ml);
169 		jta.setShowGrid(false);
170 
171 		JScrollPane jsp = new JScrollPane(jta);
172 		jpPeers.add(jsp, BorderLayout.CENTER);
173 		//jta.setPreferredScrollableViewportSize(new Dimension(250, 200));
174 
175 		// server label
176 		JPanel jpServer = new JPanel(new BorderLayout());
177 		jpServer.setBorder(new EmptyBorder(5, 5, 5, 5));
178 	    
179 		jlServer = new JLabel(" ");
180 		jpServer.add(jlServer, BorderLayout.CENTER);
181 
182 		jpPeers.add(jpServer, BorderLayout.SOUTH);
183 	    
184 		// input
185 		Box boxBottom = new Box(BoxLayout.X_AXIS);
186 
187 		Command[] commands = channel.getCommands();
188 		if (commands != null) {
189 			CommandMenuActionListener listener 
190 				= new CommandMenuActionListener();
191 			JMenu commandMenu = new JMenu("Commands");
192 			for (int i = 0; i < commands.length; i++) {
193 				String name = commands[i].getCommand() + " " 
194 					+ commands[i].getParameter();
195 				JMenuItem item = new JMenuItem(name);
196 				item.addActionListener(listener);
197 				item.setActionCommand(name);
198 				commandMenu.add(item);
199 			}
200 
201 			MenuAction commandMenuAction 
202 				= new MenuAction(commandMenu, "run.png");
203 			boxBottom.add(new XNapToggleButton(commandMenuAction));
204 			boxBottom.add(Box.createHorizontalStrut(5));
205 		}
206 
207 		//  jteInput = new HistoryTextField("", 20);
208 		jteInput = new XNapTextField("", 20);
209 		jteInput.setPreferences("chat");
210 		GUIHelper.bindEnterKey(jteInput, acSend);
211 		boxBottom.add(jteInput);
212 
213 		boxBottom.add(Box.createHorizontalStrut(5));
214 
215 		boxBottom.add(new JButton(acSend));
216 
217 		// split pane
218 		jspH = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
219 		jspH.add(cpChat, JSplitPane.LEFT);
220 		jspH.add(jpPeers, JSplitPane.RIGHT);
221 		jspH.setDividerLocation(prefs.getChatVerticalDividerLocation());
222 		jspH.setResizeWeight(1);
223 		jspH.setOneTouchExpandable(true);
224 		jspH.setBorder(GUIHelper.createEmptyBorder());
225 	
226 		// content
227 		setLayout(new BorderLayout());
228 		add(boxTop, BorderLayout.NORTH);
229 		add(jspH, BorderLayout.CENTER);
230 		add(boxBottom, BorderLayout.SOUTH);
231 		
232 		addComponentListener(new FocusHandler(jteInput));
233     }
234 
235 	public void channelJoined(ChannelEvent e)
236 	{
237 		parent.setTabTitle(this, channel.getName());
238 	}
239 
240 	public void channelParted(ChannelEvent e)
241 	{
242 		parent.setTabTitle(this, "(" + channel.getName() + ")");
243 	}
244 
245     public Action[] getActions()
246     {
247 		return getChannel().getActions();
248 		//return new Action[] { null, acMenu };
249     }
250 
251     public Channel getChannel()
252     {
253 		return channel;
254     }
255 
256 	public Command getCommand(String name)
257 	{
258 		return (executer != null) 
259 			? executer.getCommand(name) 
260 			: Executer.getCommand(name);
261 	}
262 
263 	public Command[] getCommands()
264 	{
265 		return (executer != null) 
266 			? executer.getCommands()
267 			: Executer.getCommands();
268 	}
269 
270     public Peer[] getPeers()
271     {
272 		int[] rows = jta.getSelectedRows();
273 		Peer[] peers = new Peer[rows.length];
274 		for (int i = 0; i < rows.length; i++) {
275 			peers[i] = ptm.get(rows[i]);
276 		}
277 
278 		return peers;
279     }
280 
281     /***
282      * Returns true.
283      */
284     public boolean isEchoing()
285 	{
286 		return true;
287 	}
288 
289     public void messageReceived(ChannelEvent e)
290     {
291 		Peer sender = e.getPeer();
292 		if (e.getMessageType() == ChannelEvent.MESSAGE_TYPE_MESSAGE) {
293 			printTimeStamp();
294 			print(sender.getName(), "chatUser");
295 			print("> " + e.getMessage(), "chatMessage");
296 			println("", "chatMessage", !channel.isLocal(sender));
297 		}
298 		else if (e.getMessageType()
299 				 == ChannelEvent.MESSAGE_TYPE_ACTION_MESSAGE) {
300 			println("\003\006* " + sender.getName() + " " + e.getMessage(),
301 					"chatMessage");
302 		}			
303 		else if (e.getMessageType() == ChannelEvent.MESSAGE_TYPE_ERROR) {
304 			println(e.getMessage(), "chatError");
305 		}
306 		else if (e.getMessageType() == ChannelEvent.MESSAGE_TYPE_INFO) {
307 			println(e.getMessage(), "chatInfo");
308 		}
309 
310 		if (jcbBlinkOnChatMessage.isSelected()) {
311 			Runnable runner = new Runnable()
312 				{
313 					public void run()
314 					{
315 						XNapFrame.getInstance().chatBlink();
316 					}
317 				};
318 			SwingUtilities.invokeLater(runner);
319 		}
320     }
321 
322     public void print(String s, String style)
323     {
324 		cpChat.appendLater(s, style);	
325     }
326 
327     public void println(String s, String style, boolean beep)
328     {
329 		if (s.length() > 0) {
330 			printTimeStamp();
331 		}
332 		print(s + "\n", style);
333 		if (beep && jcbBeepOnChatMessage.isSelected()) {
334 			Toolkit.getDefaultToolkit().beep();
335 		}
336     }
337 
338     public void println(String s, String style)
339     {
340 		println(s, style, true);
341     }
342 
343 	/***
344 	 * Invoked by {@link xnap.cmdl.Command} objects.
345 	 *
346 	 * @see xnap.cmdl.Console#println(String)
347 	 */
348     public void println(String text)
349 	{
350 		println(text, "chatInfo", false);
351 	}
352 
353     public void printTimeStamp()
354     {
355 		if (jcbShowChatMsgTime.isSelected()) {
356 			cpChat.appendLater("[" + Formatter.shortDate() + "]  ",
357 							   "chatMessage");
358 		}
359     }
360 
361     public String readln(String prompt)
362 	{
363 		return JOptionPane.showInputDialog
364 			(this, prompt, XNap.tr("Console"), JOptionPane.QUESTION_MESSAGE);
365 	}
366 
367     public void propertyChange(PropertyChangeEvent e)
368     {
369 		prefs.setChatVerticalDividerLocation(jspH.getDividerLocation());
370     }
371 
372     public void updateStatus()
373     {
374 		StringBuffer sb = new StringBuffer();
375 
376 		if (getChannel().getProvider() != null) {
377 			sb.append(getChannel().getProvider().getName());
378 			sb.append(" / ");
379 		}
380 		sb.append(ptm.getRowCount());
381 		sb.append(" ");
382 		sb.append(XNap.tr("users"));
383 
384 		final String s = sb.toString();
385 		Runnable runner = new Runnable()
386 			{
387 				public void run()
388 				{
389 					jlServer.setText(s);
390 				}
391 			};
392 		SwingUtilities.invokeLater(runner);
393     }
394 
395     public void peerAdded(final ChannelEvent e)
396     {
397 		Runnable runner = new Runnable()
398 			{
399 				public void run()
400 				{
401 					ptm.add(e.getPeer());
402 					updateStatus();
403 				}
404 			};
405 		SwingUtilities.invokeLater(runner);
406     }
407 
408     public void peerChanged(final ChannelEvent e)
409     {
410 		Runnable runner = new Runnable()
411 			{
412 				public void run()
413 				{
414 					ptm.changed(e.getPeer());
415 				}
416 			};
417 		SwingUtilities.invokeLater(runner);
418     }
419 
420     public void peerRemoved(final ChannelEvent e)
421     {
422 		Runnable runner = new Runnable()
423 			{
424 				public void run()
425 				{
426 					ptm.remove(e.getPeer());
427 					updateStatus();
428 				}
429 			};
430 		SwingUtilities.invokeLater(runner);
431     }
432 
433 	public void setTopic(String topic)
434 	{
435 		jlTopic.setText(topic);
436 		//  jlTopic.setToolTipText(topic);
437 	}
438 
439     public void topicChanged(final ChannelEvent e)
440     {
441 		Runnable runner = new Runnable()
442 			{
443 				public void run()
444 				{
445 					setTopic(e.getTopic());
446 				}
447 			};
448 		SwingUtilities.invokeLater(runner);
449     }
450 
451     //--- Inner Class(es) ---
452 
453     /***
454      * Sends a message.
455      */
456     private class SendAction extends AbstractAction {
457 
458         public SendAction() 
459 		{
460             putValue(Action.NAME, XNap.tr("Send"));
461             putValue(Action.SHORT_DESCRIPTION, XNap.tr("Sends message."));
462             putValue(Action.MNEMONIC_KEY, new Integer('S'));
463         }
464 
465         public void actionPerformed(ActionEvent event) 
466 		{
467 			String s = jteInput.getText();
468 			if (s.length() > 0) {
469 				jteInput.getCompletionModel().insert(s);
470 				if (s.startsWith("/")) {
471 					if (Executer.parse(s, ChannelPanel.this)) {
472 						jteInput.setText("");
473 						return;
474 					}
475 				}
476 				channel.sendMessage(s);
477 				jteInput.setText("");
478 			}
479         }
480 
481     }
482 
483     /***
484      * Inserts a command into the input field.
485      */
486     private class CommandMenuActionListener 
487 		implements ActionListener {
488 
489         public void actionPerformed(ActionEvent event) 
490 		{
491 			String command = event.getActionCommand();
492 			if (command != null) {
493 				StringTokenizer t
494 					= new StringTokenizer(command, " ");
495 				if (t.hasMoreTokens()) {
496 					String cmd = "/" + t.nextToken() + " ";
497 					String parameter
498 						= (t.hasMoreTokens()) ? t.nextToken("") : "";
499 					jteInput.setText(cmd + parameter);
500 					jteInput.setCaretPosition(cmd.length());
501 					jteInput.setSelectionStart(cmd.length());
502 					jteInput.setSelectionEnd
503 						(cmd.length() + parameter.length());
504 					jteInput.requestFocus();
505 				}
506 			}
507         }
508 
509     }
510 
511     private class ChatPeerMenu extends PeerMenu
512     {
513     
514 		//--- Constant(s) ---
515 	
516 		//--- Data field(s) ---
517 	
518 		//--- Constructor(s) ---
519 
520 		public ChatPeerMenu(PeerProvider pp)
521 		{
522 			super(pp);
523 		}
524 	
525 		//--- Method(s) ---
526 	
527 		protected void willBecomeVisible()
528 		{
529 			super.willBecomeVisible();
530 	    
531 			Peer[] peers = getPeers();
532 			if (peers != null && peers.length == 1) {
533 				Action[] actions = channel.getPeerActions(peers[0]);
534 				if (actions != null) {
535 					addTemporary(new JPopupMenu.Separator(), 0);
536 					for (int i = 0; i < actions.length; i++) {
537 						addTemporary(new XNapMenuItem(actions[i]), i);
538 					}
539 				}
540 			}
541 		}
542     }
543 }
544 
545