001 /*
002 Copyright (C) 2003 Adam Olsen
003
004 This program is free software; you can redistribute it and/or modify
005 it under the terms of the GNU General Public License as published by
006 the Free Software Foundation; either version 1, or (at your option)
007 any later version.
008
009 This program is distributed in the hope that it will be useful,
010 but WITHOUT ANY WARRANTY; without even the implied warranty of
011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012 GNU General Public License for more details.
013
014 You should have received a copy of the GNU General Public License
015 along with this program; if not, write to the Free Software
016 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
017 */
018
019 package com.valhalla.jbother.groupchat;
020
021 import java.awt.*;
022 import java.awt.event.*;
023 import java.beans.*;
024 import java.io.File;
025 import java.util.*;
026 import java.util.regex.*;
027
028 import javax.swing.*;
029 import javax.swing.text.JTextComponent;
030 import javax.swing.text.html.HTMLDocument;
031
032 import org.jivesoftware.smack.*;
033 import org.jivesoftware.smack.packet.*;
034 import org.jivesoftware.smackx.Form;
035 import org.jivesoftware.smackx.muc.DiscussionHistory;
036 import org.jivesoftware.smackx.muc.MultiUserChat;
037 import org.jivesoftware.smackx.packet.MUCUser;
038
039 import com.valhalla.gui.*;
040 import com.valhalla.jbother.*;
041 import com.valhalla.jbother.jabber.BuddyStatus;
042 import com.valhalla.jbother.jabber.MUCBuddyStatus;
043 import com.valhalla.jbother.jabber.smack.InvitationRejectionPacketListener;
044 import com.valhalla.jbother.plugins.events.MUCEvent;
045 import com.valhalla.pluginmanager.PluginChain;
046 import com.valhalla.settings.Settings;
047 import net.infonode.tabbedpanel.*;
048 import net.infonode.tabbedpanel.titledtab.*;
049 import net.infonode.util.*;
050
051 /**
052 * This is the panel that contains a groupchat conversation. It is placed in a
053 * JTabbedPane in GroupChat frame.
054 *
055 * @author Adam Olsen
056 */
057 public class ChatRoomPanel extends JPanel implements LogViewerCaller,
058 TabFramePanel, UserChooserListener {
059 private ResourceBundle resources = ResourceBundle.getBundle(
060 "JBotherBundle", Locale.getDefault());
061
062 private MJTextArea textEntryArea = new MJTextArea(true,2, 0);
063
064 private StringBuffer conversationText = new StringBuffer();
065
066 private ConversationArea conversationArea = new ConversationArea();
067
068 private JMenuItem logItem = new JMenuItem(resources.getString("viewLog")),
069 newItem = new JMenuItem(resources.getString("joinRoom")),
070 leaveItem = new JMenuItem(resources.getString("leaveRoom")),
071 nickItem = new JMenuItem(resources.getString("changeNickname")),
072 registerItem = new JMenuItem(resources.getString("registerForRoom")),
073 viewAdmins = new JMenuItem(resources.getString("viewAdmins")),
074 viewModerators = new JMenuItem(resources
075 .getString("viewModerators")), viewMembers = new JMenuItem(
076 resources.getString("viewMembers")),
077 viewParticipants = new JMenuItem(resources
078 .getString("viewParticipants")),
079 viewOwners = new JMenuItem(resources.getString("viewOwners")),
080 viewOutcasts = new JMenuItem(resources.getString("viewOutcasts")),
081 destroyRoom = new JMenuItem(resources.getString("destroyRoom")),
082 invite = new JMenuItem(resources.getString("inviteUser"));
083
084 private JPopupMenu popMenu = new JPopupMenu();
085 private TitledTab tab;
086
087 private int oldMaximum = 0;
088
089 private JPanel scrollPanel = new JPanel(new GridLayout(1, 0));
090
091 private JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
092
093 private MultiUserChat chat;
094
095 private String chatroom, nickname, pass;
096
097 private Hashtable buddyStatuses = new Hashtable();
098
099 private GroupChatNickList nickList;
100
101 private String subject = resources.getString("noSubject");
102
103 private MJTextField subjectField = new MJTextField();
104
105 private boolean listenersAdded = false;
106
107 private GroupParticipantListener participantListener = new GroupParticipantListener(this);
108
109 private InvitationRejectionPacketListener invitationRejectionPacketListener = new InvitationRejectionPacketListener();
110
111 private GroupChatMessagePacketListener messageListener = new GroupChatMessagePacketListener(
112 this);
113
114 private SubjectListener subjectListener = new SubjectListener(this);
115
116 private StatusListener statusListener = new StatusListener(this);
117
118 private UserStatusListener userStatusListener = new UserStatusListener(this);
119
120 private boolean messageToMe = false;
121 private boolean removed = false;
122 private int joins = 0;
123
124 /**
125 * This sets up the appearance of the chatroom window
126 *
127 * @param chatroom
128 * the chatroom address
129 * @param nickname
130 * the nickname to use when joining
131 */
132 public ChatRoomPanel(String chatroom, String nickname, String pass) {
133 this.chatroom = chatroom;
134 this.nickname = nickname;
135 this.pass = pass;
136 chat = new MultiUserChat(BuddyList.getInstance().getConnection(),
137 chatroom);
138
139 BuddyList.getInstance().startTabFrame();
140
141 setLayout(new BorderLayout(5, 5));
142 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
143 subjectField.setText(resources.getString("noSubject"));
144
145 JPanel subjectPanel = new JPanel();
146 subjectPanel.setLayout(new BorderLayout());
147 subjectPanel.add(new JLabel("<html><b>"
148 + resources.getString("subject") + ": </b></html>"),
149 BorderLayout.WEST);
150 subjectPanel.add(subjectField, BorderLayout.CENTER);
151
152 add(subjectPanel, BorderLayout.NORTH);
153
154 add(mainPanel);
155
156 nickList = new GroupChatNickList(this);
157
158 String divLocString = Settings.getInstance().getProperty(
159 "chatWindowDividerLocation");
160 int divLoc = 0;
161 Dimension dimension = BuddyList.getInstance().getTabFrame().getSize();
162
163 if (divLocString != null) {
164 divLoc = Integer.parseInt(divLocString);
165 } else {
166 divLoc = (int) dimension.getWidth() - 127;
167 }
168
169 if (divLoc == 0)
170 divLoc = (int) dimension.getWidth() - 127;
171
172 mainPanel.setDividerLocation(divLoc);
173 mainPanel.setOneTouchExpandable(true);
174 mainPanel.setResizeWeight(1);
175 mainPanel.addPropertyChangeListener("lastDividerLocation",
176 new DividerListener("chatWindowDividerLocation"));
177
178 conversationArea.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
179
180 setUpPopMenu();
181
182 scrollPanel.add(conversationArea);
183
184 JSplitPane containerPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
185 scrollPanel, new JScrollPane(textEntryArea));
186 containerPanel.setResizeWeight(1);
187
188 textEntryArea.setLineWrap(true);
189 textEntryArea.setWrapStyleWord(true);
190
191 divLocString = Settings.getInstance().getProperty(
192 "chatRoomPanelDividerLocation");
193 divLoc = 0;
194
195 try {
196 if (divLocString != null) {
197 divLoc = Integer.parseInt(divLocString);
198 } else {
199 divLoc = (int) dimension.getWidth() - 100;
200 Settings.getInstance().setProperty(
201 "chatRoomPanelDividerLocation", divLoc + "" );
202 }
203 } catch (NumberFormatException ex) {
204 }
205
206 if (divLoc == 0)
207 divLoc = 290;
208 containerPanel.setDividerLocation(divLoc);
209 containerPanel.addPropertyChangeListener("lastDividerLocation",
210 new DividerListener("chatRoomPanelDividerLocation"));
211 containerPanel.repaint();
212
213 mainPanel.add(containerPanel);
214 mainPanel.add(nickList);
215
216 textEntryArea.grabFocus();
217 textEntryArea.setFocusTraversalKeysEnabled(false); //for disable focus
218 // traversal with TAB
219 setSubject(subject);
220
221 addListeners();
222 }
223
224 public void removed() { removed = true; }
225
226 public void updateStyle(Font font){}
227
228 public void setTab( TitledTab tab ) { this.tab = tab; }
229 public TitledTab getTab() { return tab; }
230
231 public ConversationArea getConversationArea() {
232 return conversationArea;
233 }
234
235 public GroupChatNickList getGroupChatNickList() {
236 return nickList;
237 }
238
239 public void removeMe()
240 {
241 nickList.removeBuddy(chatroom + "/" + nickname);
242 }
243
244 public void disconnect() {
245 if(nickList != null) nickList.clear();
246 }
247
248 public void doAction(String command, MUCBuddyStatus buddy) {
249 MUCUser user = buddy.getMUCUser();
250 if (user == null) {
251 serverErrorMessage(resources.getString("jidNotFound"));
252 return;
253 }
254
255 MUCUser.Item item = user.getItem();
256
257 if (item == null) {
258 serverErrorMessage(resources.getString("jidNotFound"));
259 return;
260 }
261
262 if (item.getJid() == null) {
263 serverErrorMessage(resources.getString("jidNotFound"));
264 return;
265 }
266
267 com.valhalla.Logger.debug("Running " + command + " on "
268 + buddy.getUser());
269 Thread thread = new Thread(new RunTaskThread(resources
270 .getString("error")
271 + ": ", command, item.getJid()));
272 thread.start();
273 }
274
275 public String getUser() {
276 return chat.getRoom() + "/" + chat.getNickname();
277 }
278
279 /**
280 * @return true if the TabFrame panel listeners have already been added to
281 * this panel
282 */
283 public boolean listenersAdded() {
284 return listenersAdded;
285 }
286
287 /**
288 * Sets whether or not the TabFrame panel listeners have been added
289 *
290 * @param added
291 * true if they have been added
292 */
293 public void setListenersAdded(boolean added) {
294 this.listenersAdded = added;
295 }
296
297 /**
298 * @return the input area of this panel
299 */
300 public JComponent getInputComponent() {
301 return textEntryArea;
302 }
303
304 /**
305 * @return the JList representing the nicklist
306 */
307 public JList getNickList() {
308 return nickList.getList();
309 }
310
311 /**
312 * @return the text entry area
313 */
314 public JTextComponent getTextEntryArea() {
315 return textEntryArea;
316 }
317
318 /**
319 * Listens for a change in the divider location, and saves it for later
320 * retreival
321 *
322 * @author Adam Olsen
323 * @version 1.0
324 */
325 private class DividerListener implements PropertyChangeListener {
326 String prop;
327
328 public DividerListener(String prop) {
329 this.prop = prop;
330 }
331
332 public void propertyChange(PropertyChangeEvent e) {
333 if (e.getOldValue().toString().equals("-1"))
334 return;
335 Settings.getInstance()
336 .setProperty(prop, e.getOldValue().toString());
337 BuddyList.getInstance().getTabFrame().saveStates();
338 }
339 }
340
341
342 /**
343 * Look for a right click, and show a pop up menu
344 *
345 * @author Adam Olsen
346 * @version 1.0
347 */
348 class RightClickListener extends MouseAdapter {
349 public void mousePressed(MouseEvent e) {
350 checkPop(e);
351 }
352
353 public void mouseReleased(MouseEvent e) {
354 checkPop(e);
355 }
356
357 public void mouseClicked(MouseEvent e) {
358 checkPop(e);
359 }
360
361 public void checkPop(MouseEvent e) {
362 // look for the popup trigger.. usually a right click
363 if (e.isPopupTrigger()) {
364 if (conversationArea.getSelectedText() == null) {
365 popMenu.show(e.getComponent(), e.getX(), e.getY());
366 }
367 }
368 }
369 }
370
371 /**
372 * Add the various menu items to the popup menu
373 */
374 private void setUpPopMenu() {
375 MenuItemListener listener = new MenuItemListener();
376
377 conversationArea.getTextPane().addMouseListener(new RightClickListener());
378 CopyPasteContextMenu.registerComponent(conversationArea.getTextPane());
379
380 popMenu.add(nickItem);
381 popMenu.add(newItem);
382 popMenu.add(logItem);
383
384 popMenu.addSeparator();
385
386 popMenu.add(viewAdmins);
387 popMenu.add(viewModerators);
388 popMenu.add(viewMembers);
389 popMenu.add(viewParticipants);
390 popMenu.add(viewOwners);
391 popMenu.add(viewOutcasts);
392 popMenu.add(invite);
393 popMenu.add(registerItem);
394 popMenu.add(destroyRoom);
395
396 popMenu.addSeparator();
397 popMenu.add(leaveItem);
398
399 logItem.addActionListener(listener);
400 newItem.addActionListener(listener);
401 leaveItem.addActionListener(listener);
402 nickItem.addActionListener(listener);
403 registerItem.addActionListener(listener);
404 viewAdmins.addActionListener(listener);
405 viewOutcasts.addActionListener(listener);
406 viewMembers.addActionListener(listener);
407 invite.addActionListener(listener);
408 viewParticipants.addActionListener(listener);
409 viewOwners.addActionListener(listener);
410 viewModerators.addActionListener(listener);
411 destroyRoom.addActionListener(listener);
412 }
413
414 /**
415 * Listens for items to be selected in the menu
416 *
417 * @author Adam Olsen
418 * @version 1.0
419 */
420 private class MenuItemListener implements ActionListener {
421 public void actionPerformed(ActionEvent e) {
422 if (e.getSource() == nickItem)
423 changeNickHandler();
424 else if (e.getSource() == leaveItem) {
425 BuddyList.getInstance().getTabFrame().removePanel(ChatRoomPanel.this);
426 BuddyList.getInstance().stopTabFrame();
427 } else if (e.getSource() == newItem) {
428 GroupChatBookmarks gc = new GroupChatBookmarks(BuddyList
429 .getInstance().getTabFrame());
430 gc.load();
431 gc.setVisible(true);
432 gc.toFront();
433 } else if (e.getSource() == logItem)
434 new LogViewerDialog(ChatRoomPanel.this, getRoomName());
435 else if (e.getSource() == registerItem)
436 configurationHandler("registerFor");
437 else if (e.getSource() == viewAdmins)
438 new ListViewDialog(ChatRoomPanel.this, ListViewDialog.TYPE_ADMIN);
439 else if (e.getSource() == viewOutcasts)
440 new ListViewDialog(ChatRoomPanel.this, ListViewDialog.TYPE_OUTCASTS);
441 else if (e.getSource() == viewMembers)
442 new ListViewDialog(ChatRoomPanel.this, ListViewDialog.TYPE_MEMBERS);
443 else if (e.getSource() == viewParticipants)
444 new ListViewDialog(ChatRoomPanel.this,
445 ListViewDialog.TYPE_PARTICIPANTS);
446 else if (e.getSource() == viewOwners)
447 new ListViewDialog(ChatRoomPanel.this, ListViewDialog.TYPE_OWNERS);
448 else if (e.getSource() == invite)
449 {
450 UserChooser chooser = new UserChooser(BuddyList.getInstance().getTabFrame(), resources.getString("inviteUser"));
451 chooser.addListener(ChatRoomPanel.this);
452 chooser.setVisible(true);
453 }
454
455 else if (e.getSource() == viewModerators)
456 new ListViewDialog(ChatRoomPanel.this, ListViewDialog.TYPE_MODERATORS);
457 else if (e.getSource() == destroyRoom)
458 destroyHandler();
459 }
460 }
461
462 public void usersChosen(UserChooser.Item[] items)
463 {
464 usersChosen((Object[])items);
465 }
466
467 /**
468 * Description of the Method
469 *
470 * @param array Description of the Parameter
471 */
472 public void usersChosen(Object[] us) {
473 String result = (String) JOptionPane.showInputDialog(BuddyList.getInstance().getTabFrame(), resources
474 .getString("enterReasonForInvite"), resources
475 .getString("inviteUser"), JOptionPane.QUESTION_MESSAGE, null,
476 null, "Come join us!");
477
478
479 if(result == null || result.equals("")) return;
480
481 if(us[0] instanceof String)
482 {
483 inviteUsers(new String[] {(String)us[0]}, result);
484 return;
485 }
486
487 ArrayList u = new ArrayList();
488 for(int i = 0; i < us.length; i++)
489 {
490 UserChooser.Item item = (UserChooser.Item)us[i];
491 u.add(item.getJID());
492 }
493 inviteUsers((String[])u.toArray(new String[u.size()]), result);
494 }
495
496
497 protected void inviteUsers(final String[] users, final String reason) {
498 Thread thread = new Thread(new Runnable() {
499 public void run() {
500 for(int i = 0; i < users.length; i++)
501 {
502 chat.invite(users[i], reason);
503 }
504 }
505 });
506
507 thread.start();
508
509 }
510
511 private void destroyHandler() {
512 final int r = JOptionPane.showConfirmDialog(BuddyList.getInstance()
513 .getTabFrame(), resources.getString("sureDestroyRoom"),
514 resources.getString("destroyRoom"), JOptionPane.YES_NO_OPTION);
515
516 if (r == JOptionPane.YES_OPTION) {
517 final String reason = (String) JOptionPane.showInputDialog(
518 BuddyList.getInstance().getTabFrame(), resources
519 .getString("enterReasonForDestroy"), resources
520 .getString("destroyRoom"),
521 JOptionPane.QUESTION_MESSAGE, null, null,
522 "Room has been moved");
523
524 if (reason == null)
525 return;
526
527 final String result = (String) JOptionPane.showInputDialog(
528 BuddyList.getInstance().getTabFrame(), resources
529 .getString("enterAlternate"), resources
530 .getString("destroyRoom"),
531 JOptionPane.QUESTION_MESSAGE, null, null, "");
532
533 if (result == null)
534 return;
535
536 new Thread(new DestroyThread(reason, result)).start();
537 }
538 }
539
540 class DestroyThread implements Runnable {
541 String reason, result;
542
543 public DestroyThread(String reason, String result) {
544 this.reason = reason;
545 this.result = result;
546 }
547
548 public void run() {
549 String error = null;
550 try {
551 chat.destroy(reason, result);
552 } catch (XMPPException ex) {
553 error = ex.getMessage();
554 }
555
556 final String e = error;
557
558 SwingUtilities.invokeLater(new Runnable() {
559 public void run() {
560 if (e != null) {
561 serverErrorMessage(e);
562 } else {
563 serverErrorMessage("Room has been destroyed");
564 }
565 }
566 });
567 }
568 }
569
570 /**
571 * Collects a Data Form to be filled out
572 *
573 * @param type
574 * the type of form. Either "configure" or "registerFor"
575 */
576 public void configurationHandler(String type) {
577 Thread thread = new Thread(new ConfigThread(type));
578 thread.start();
579 }
580
581 /**
582 * Collects the data form and displays it.
583 *
584 * @author Adam Olsen
585 */
586 private class ConfigThread implements Runnable {
587 private String type = "configure";
588
589 /**
590 * @param type
591 * the type of form to collect.
592 */
593 public ConfigThread(String type) {
594 this.type = type;
595 }
596
597 /**
598 * Called by the enclosing thread
599 */
600 public void run() {
601 try {
602 Form temp;
603
604 // get the form
605 if (type.equals("configure")) {
606 temp = chat.getConfigurationForm();
607 } else {
608 temp = chat.getRegistrationForm();
609 }
610
611 if (temp == null) {
612 serverErrorMessage(resources
613 .getString("couldNotCollectForm"));
614 return;
615 }
616
617 final Form form = temp;
618 final JBDataForm f = new JBDataForm(BuddyList.getInstance().getTabFrame(), form);
619 f.addActionListener(new ActionListener() {
620 public void actionPerformed(ActionEvent e) {
621 // if the cancel button is pressed, close the form
622 if (e.getActionCommand().equals("cancel")) {
623 SwingUtilities.invokeLater(new Runnable() {
624 public void run() {
625 f.dispose();
626 }
627 });
628 }
629
630 // else submit the form
631 else if (e.getActionCommand().equals("ok")) {
632 SwingUtilities.invokeLater(new Runnable() {
633 public void run() {
634 if (submitConfigurationForm(f, type)) {
635 f.setVisible(false);
636 }
637 }
638 });
639 }
640 }
641 });
642
643 // show the form
644 SwingUtilities.invokeLater(new Runnable() {
645 public void run() {
646 f.setVisible(true);
647 }
648 });
649 }
650
651 // if there was an error collecting the form
652 catch (XMPPException ex) {
653 String message = ex.getMessage();
654 if (ex.getXMPPError() != null) {
655 message = resources.getString("xmppError"
656 + ex.getXMPPError().getCode());
657 }
658
659 serverErrorMessage(resources.getString(type + "Room") + ": "
660 + message);
661 }
662 }
663 }
664
665 /**
666 * Submits the form
667 *
668 * @param form
669 * the form to submit
670 * @param type
671 * the type of form, either "configure" or "registerFor"
672 * @return false if the required fields have not been filled out
673 */
674 private boolean submitConfigurationForm(JBDataForm form, final String type) {
675 final Form answer = form.getAnswerForm();
676 if (answer == null)
677 return false;
678
679 Thread thread = new Thread(new Runnable() {
680 public void run() {
681 try {
682 if (type.equals("configure")) {
683 chat.sendConfigurationForm(answer);
684 } else {
685 chat.sendRegistrationForm(answer);
686 }
687 } catch (XMPPException ex) {
688 }
689
690 SwingUtilities.invokeLater(new Runnable() {
691 public void run() {
692 if (type.equals("configure")) {
693 serverNoticeMessage(resources
694 .getString("configureSubmitted"));
695 } else {
696 serverNoticeMessage(resources
697 .getString("registerSubmitted"));
698 }
699 }
700 });
701 }
702 });
703 thread.start();
704
705 return true;
706 }
707
708 /**
709 * Opens the log window for this chat room
710 */
711 public void openLogWindow() {
712 new LogViewerDialog(this, getRoomName());
713 }
714
715 /**
716 * Adds a buddy to the nickname list
717 *
718 * @param buddy
719 * the buddy to add
720 */
721 public void addBuddy(String buddy) {
722 if( nickList == null || buddy == null ) return;
723 nickList.addBuddy(buddy);
724 }
725
726 /**
727 * Removes a buddy from the nick list
728 *
729 * @param buddy
730 * the buddy to remove
731 */
732 public void removeBuddy(String buddy) {
733 if( nickList == null || buddy == null ) return;
734
735 nickList.removeBuddy(buddy);
736 }
737
738 /**
739 * Opens the log file
740 */
741 public void startLog() {
742 // for loggingg
743 if (Settings.getInstance().getBoolean("keepLogs")) {
744 String logFileName = LogViewerDialog.getDateName() + ".log";
745 String logFileDir = JBother.profileDir
746 + File.separatorChar
747 + "logs"
748 + File.separatorChar
749 + getRoomName().replaceAll("@", "_at_").replaceAll("\\/",
750 "-");
751
752 File logDir = new File(logFileDir);
753
754 if (!logDir.isDirectory() && !logDir.mkdirs())
755 Standard.warningMessage(this, resources.getString("log"),
756 resources.getString("couldNotCreateLogDir"));
757
758 String logEnc =
759 Settings.getInstance().getProperty("keepLogsEncoding");
760 conversationArea.setLogFile(new File(logDir, logFileName), logEnc);
761 }
762 }
763
764 /**
765 * Gets the BuddyStatus represending a user in the room
766 *
767 * @param user the BuddyStatus to get
768 * @return the requested BuddyStatus
769 */
770 public MUCBuddyStatus getBuddyStatus(String user) {
771 if (!buddyStatuses.containsKey(user)) {
772 MUCBuddyStatus buddy = new MUCBuddyStatus(user);
773 buddy.setMUC(chat);
774 buddy.setName(user.substring(user.indexOf("/") + 1, user.length()));
775 buddyStatuses.put(user, buddy);
776 }
777
778 return (MUCBuddyStatus) buddyStatuses.get(user);
779 }
780
781 /**
782 * @return the tooltip for this panel (when hovering over the tab in the
783 * tab frame
784 */
785 public String getPanelToolTip()
786 {
787 return getWindowTitle();
788 }
789
790 /**
791 * Returns the tab name for the TabFramePanel
792 *
793 * @return the panel name
794 */
795 public String getPanelName() {
796 String n = getShortRoomName().replaceAll( "%.*", "" );
797 if (n.length() >= 10 )
798 {
799 n = n.substring(0, 7) + "...";
800 }
801 return n;
802 }
803
804 /**
805 * Returns the tooltip for the tab in the TabFrame
806 *
807 * @return the tooltip for this tab in the tab frame
808 */
809 public String getTooltip() {
810 return getRoomName();
811 }
812
813 /**
814 * Returns the window title
815 *
816 * @return the window title for the TabFrame when this tab is selected
817 */
818 public String getWindowTitle() {
819 return resources.getString("groupChat") + ": " + getRoomName();
820 }
821
822 /**
823 * Gets the short room name - for example, if you are talking in
824 * jdev@conference.jabber.org, it would return "jdev"
825 *
826 * @return short room name
827 */
828 public String getShortRoomName() {
829 return chatroom.replaceAll("\\@.*", "");
830 }
831
832 /**
833 * Gets the entire room name, server included
834 *
835 * @return gets the room address
836 */
837 public String getRoomName() {
838 return chatroom;
839 }
840
841 /**
842 * Returns the nickname of a user in a group chat.
843 *
844 * @param id
845 * The full id of someone in a room, ie:
846 * jdev@conference.jabber.org/synic
847 * @return the nickname of the person in the room
848 */
849 public String getNickname(String id) {
850 int index = id.indexOf("/");
851 if (index == -1)
852 return id;
853 return id.substring(index + 1);
854 }
855
856 public MultiUserChat getChat() {
857 return chat;
858 }
859
860 public GroupParticipantListener getParticipantListener() {
861 return participantListener;
862 }
863
864 /**
865 * Starts the groupchat. Sets up a thread to connect, and start that thread
866 */
867 public void startChat() {
868 if (!BuddyList.getInstance().checkConnection())
869 return;
870
871 serverNoticeMessage("Connecting to " + getRoomName() + " ... ");
872 BuddyList.getInstance().addTabPanel(this);
873
874 JoinChatThread t = new JoinChatThread();
875 t.start();
876
877 startLog();
878 }
879
880 private void leaveChat() {
881 if (chat == null)
882 return;
883
884 buddyStatuses.clear();
885 com.valhalla.Logger.debug("Leaving " + chat.getRoom());
886 Presence p = new Presence(Presence.Type.UNAVAILABLE);
887 p.setTo(chat.getRoom());
888 if (BuddyList.getInstance().checkConnection())
889 BuddyList.getInstance().getConnection().sendPacket(p);
890 chat.removeMessageListener(messageListener);
891 chat.removeParticipantListener(participantListener);
892 chat.removeSubjectUpdatedListener(subjectListener);
893 chat.removeParticipantStatusListener(statusListener);
894 chat.removeUserStatusListener(userStatusListener);
895 chat.removeInvitationRejectionListener(invitationRejectionPacketListener);
896
897 chat = null;
898 System.gc();
899 }
900
901 /**
902 * Asks for a new topic, then sets the new topic
903 */
904 private void topicHandler(final String subject) {
905 Thread thread = new Thread(new Runnable() {
906 public void run() {
907 try {
908 chat.changeSubject(subject);
909 } catch (final XMPPException e) {
910 SwingUtilities.invokeLater(new Runnable() {
911 public void run() {
912 String message = e.getMessage();
913 serverErrorMessage(resources
914 .getString("errorSettingSubject")
915 + ": " + message);
916 }
917 });
918 }
919
920 SwingUtilities.invokeLater(new Runnable() {
921 public void run() {
922 subjectField.setEnabled(true);
923 }
924 });
925
926 }
927 });
928
929 thread.start();
930 }
931
932 private class RunTaskThread implements Runnable {
933 private String err, method;
934
935 private Object param;
936
937 public RunTaskThread(String err, String method, Object param) {
938 this.err = err;
939 this.method = method;
940 this.param = param;
941 }
942
943 public void run() {
944 java.lang.reflect.Method m;
945 try {
946 m = chat.getClass().getMethod(this.method,
947 new Class[] { param.getClass() });
948 } catch (Exception e) {
949 e.printStackTrace();
950 return;
951 }
952
953 try {
954 m.invoke(chat, new Object[] { param });
955 } catch (final java.lang.reflect.InvocationTargetException ex) {
956 final XMPPException xmpp = (XMPPException) ex.getCause();
957 if (xmpp == null)
958 return;
959
960 SwingUtilities.invokeLater(new Runnable() {
961 public void run() {
962 serverErrorMessage(err
963 + ": "
964 + resources.getString("xmppError"
965 + xmpp.getXMPPError().getCode()));
966 }
967 });
968 } catch (Exception e) {
969 e.printStackTrace();
970 }
971 }
972 }
973
974 /**
975 * Asks for a new nickname, and sends a nickname change request
976 */
977 private void changeNickHandler() {
978 String result = (String) JOptionPane.showInputDialog(null, resources
979 .getString("enterNickname"),
980 resources.getString("setNickname"),
981 JOptionPane.QUESTION_MESSAGE, null, null, chat.getNickname());
982
983 if (result != null && !result.equals("")) {
984 Thread thread = new Thread(new RunTaskThread(resources
985 .getString("couldNotChangeNick"), "changeNickname", result));
986 thread.start();
987 }
988 }
989
990 /**
991 * Adds the event listeners for the various components in this chatwindows
992 */
993 public void addListeners() {
994 //set up the window so you can press enter in the text box and
995 //that will send the message.
996 Action SendMessageAction = new AbstractAction() {
997 public void actionPerformed(ActionEvent e) {
998 sendHandler();
999 }
1000 };
1001
1002 Action nickCompletionAction = new AbstractAction() {
1003 public void actionPerformed(ActionEvent e) {
1004 nickCompletionHandler();
1005 }
1006 };
1007
1008 //set it up so that if they drag in the conversation window, it grabs
1009 // the focus
1010 conversationArea.getTextPane().addMouseMotionListener(new MouseMotionAdapter() {
1011 public void mouseDragged(MouseEvent e) {
1012 //conversationArea.grabFocus();
1013 }
1014 });
1015
1016 //set it up so that if there isn't any selected text in the
1017 // conversation area
1018 //the textentryarea grabs the focus.
1019 conversationArea.getTextPane().addMouseListener(new MouseAdapter() {
1020 public void mouseReleased(MouseEvent e) {
1021 if (conversationArea.getSelectedText() == null) {
1022 textEntryArea.requestFocus();
1023 }
1024 }
1025 });
1026
1027 Action closeAction = new AbstractAction() {
1028 public void actionPerformed(ActionEvent e) {
1029 BuddyList.getInstance().getTabFrame().removePanel(ChatRoomPanel.this);
1030 BuddyList.getInstance().stopTabFrame();
1031 }
1032 };
1033
1034 subjectField.addActionListener(new ActionListener() {
1035 public void actionPerformed(ActionEvent e) {
1036 subjectField.setEnabled(false);
1037 topicHandler(subjectField.getText());
1038 setSubject(subject);
1039 }
1040 });
1041
1042 KeyStroke enterStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
1043 textEntryArea.getInputMap().put(enterStroke, SendMessageAction);
1044
1045 KeyStroke tabStroke = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);
1046 textEntryArea.getInputMap().put(tabStroke, nickCompletionAction);
1047
1048 textEntryArea.getInputMap().put(
1049 KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit
1050 .getDefaultToolkit().getMenuShortcutKeyMask()),
1051 closeAction);
1052
1053 }
1054
1055 /**
1056 * Leaves this room and removes it from the groupchat frame
1057 */
1058 public void leave() {
1059 closeLog();
1060 leaveChat();
1061 } //leave the chatroom
1062
1063 /**
1064 * Gets the nickname currently being used in the chat room
1065 *
1066 * @return the nickname being used in the chatroom
1067 */
1068 public String getNickname() {
1069 if (chat == null || chat.getNickname() == null)
1070 return nickname;
1071 return chat.getNickname();
1072 }
1073
1074 /**
1075 * Displays a server notice message
1076 *
1077 * @param message
1078 * the message to display
1079 */
1080 public void serverNoticeMessage(String message) {
1081 conversationArea.append(getDate(null));
1082 conversationArea.append(" -> " + message + "\n", ConversationArea.SERVER);
1083 }
1084
1085 public void serverErrorMessage(String message) {
1086 conversationArea.append(getDate(null));
1087 conversationArea.append(" -> " + message + "\n", ConversationArea.SENDER);
1088 }
1089
1090 /**
1091 * Receives a message
1092 *
1093 * @param from
1094 * who it's from
1095 * @param message
1096 * the message
1097 */
1098 public void receiveMessage(String from, String message,
1099 Date date) {
1100
1101 String curNick = nickname;
1102 if( chat != null && chat.getNickname() != null) curNick = chat.getNickname();
1103 if (from.equals("")
1104 || from.toLowerCase().equals(chat.getRoom().toLowerCase())) {
1105 //server message
1106 serverNoticeMessage(message);
1107 return;
1108 } else {
1109
1110 boolean highLightedSound = false;
1111
1112 if (message.startsWith("/me ")) {
1113 message = message.replaceAll("^\\/me ", "");
1114 conversationArea.append(getDate(date));
1115 conversationArea.append(" *" + from+ " ", ConversationArea.SENDER, true);
1116 conversationArea.append(message + "\n", ConversationArea.BLACK);
1117 } else if (message.toLowerCase().replaceAll("<[^>]*>", "").matches(
1118 ".*(^|\\W)" + curNick.toLowerCase() + "\\W.*") &&
1119 !from.toLowerCase().equals(curNick.toLowerCase())) {
1120 TabbedPanel tabPane = BuddyList.getInstance().getTabFrame()
1121 .getTabPane();
1122
1123 if (tabPane.getSelectedTab().getContentComponent() != this)
1124 messageToMe = true;
1125 conversationArea.append(getDate(date), Color.BLACK, false, ConversationArea.HL);
1126 conversationArea.append(" " +from + ": ", ConversationArea.RECEIVER, true, ConversationArea.HL);
1127 conversationArea.append(message + "\n", ConversationArea.BLACK, false, ConversationArea.HL);
1128
1129 com.valhalla.jbother.sound.SoundPlayer
1130 .play("groupHighlightedSound");
1131
1132 if (!BuddyList.getInstance().getTabFrame().isFocused()) {
1133 NotificationPopup.showSingleton(BuddyList.getInstance()
1134 .getTabFrame(), resources
1135 .getString("messageReceived"), "<b>"
1136 + resources.getString("from") + ":</b> "
1137 + from,this);
1138 }
1139
1140 highLightedSound = true;
1141 } else {
1142 conversationArea.append(getDate(date));
1143 conversationArea.append(" " + from +": ", ConversationArea.RECEIVER, true);
1144 conversationArea.append(message + "\n", ConversationArea.BLACK);
1145 }
1146
1147 if (!highLightedSound)
1148 {
1149 com.valhalla.jbother.sound.SoundPlayer
1150 .play("groupReceivedSound");
1151
1152 if( Settings.getInstance().getBoolean("usePopup") &&
1153 Settings.getInstance().getBoolean("popupForGroupMessage" ))
1154 {
1155 NotificationPopup.showSingleton(BuddyList.getInstance().getTabFrame(), resources
1156 .getString("groupMessageReceived"), from,this);
1157 }
1158 }
1159 }
1160
1161 // fire MUCEvent for message received
1162 PluginChain.fireEvent(new MUCEvent(from,
1163 MUCEvent.EVENT_MESSAGE_RECEIVED, message, date));
1164
1165 BuddyList.getInstance().getTabFrame().markTab(this, messageToMe);
1166 }
1167
1168 public void resetMessageToMe() {
1169 messageToMe = false;
1170 }
1171
1172 /**
1173 * Closes the log file
1174 */
1175 public void closeLog() {
1176 conversationArea.closeLog();
1177 }
1178
1179 /**
1180 * @return a String representing the current time the format:
1181 * [Hour:Minute:Second]
1182 */
1183 public String getDate(Date d) {
1184 return ConversationPanel.getDate(d);
1185 }
1186
1187 public MJTextField getSubjectField() {
1188 return subjectField;
1189 }
1190
1191 /**
1192 * Sets the subject of the room
1193 *
1194 * @param subject
1195 * the subject to set
1196 */
1197 public void setSubject(String subject) {
1198 this.subject = subject;
1199 if (BuddyList.getInstance().getTabFrame() != null)
1200 BuddyList.getInstance().getTabFrame().setSubject(this);
1201
1202 subjectField.setText(subject);
1203 subjectField.setCaretPosition(0);
1204 subjectField.setToolTipText(subject);
1205 }
1206
1207 /**
1208 * Returns the current room subject
1209 *
1210 * @return the current room subject
1211 */
1212 public String getSubject() {
1213 return this.subject;
1214 }
1215
1216 /**
1217 * Gets all the buddy statuses in the room
1218 *
1219 * @return all BuddyStatuses
1220 */
1221 public Hashtable getBuddyStatuses() {
1222 return this.buddyStatuses;
1223 }
1224
1225 /**
1226 * Sends the message currently in the textentryarea
1227 */
1228 private void sendHandler() {
1229 String text = textEntryArea.getText();
1230
1231 Message message = chat.createMessage();
1232 message.setBody(text);
1233
1234 if (!textEntryArea.getText().equals("")) {
1235 try {
1236 chat.sendMessage(message);
1237 } catch (XMPPException e) {
1238 com.valhalla.Logger.debug("Could not send message.");
1239 } catch (IllegalStateException ex) {
1240 serverErrorMessage(resources.getString("notConnected"));
1241 }
1242
1243 textEntryArea.setText("");
1244 }
1245 }
1246
1247 /**
1248 * Implementation of Tab nick completion in the textEntryArea
1249 */
1250 private void nickCompletionHandler() {
1251 String text = textEntryArea.getText();
1252
1253 /* if we have nothing => do nothing */
1254 if (!text.equals("")) {
1255 int caretPosition = textEntryArea.getCaretPosition();
1256 int startPosition = text.lastIndexOf(" ", caretPosition - 1) + 1;
1257 String nickPart = text.substring(startPosition, caretPosition);
1258 Vector matches = new Vector();
1259
1260 java.util.List keys = new ArrayList(buddyStatuses.keySet());
1261 Iterator iterator = keys.iterator();
1262
1263 while (iterator.hasNext()) {
1264 BuddyStatus buddy = (BuddyStatus) buddyStatuses.get(iterator
1265 .next());
1266 if (!nickList.contains(buddy))
1267 continue;
1268 try {
1269 String nick = buddy.getUser().substring(
1270 buddy.getUser().lastIndexOf("/") + 1);
1271 if (nick.toLowerCase().startsWith(nickPart.toLowerCase())) {
1272 matches.add(nick);
1273 }
1274 } catch (java.lang.NullPointerException e) {
1275 }
1276 }
1277
1278 if (matches.size() > 0) {
1279 String append = "";
1280
1281 if (matches.size() > 1) {
1282 String nickPartNew = (String) matches.firstElement();
1283 String nick = "";
1284 String hint = nickPartNew + ", ";
1285 int nickPartLen = nickPart.length();
1286 for (int i = 1; i < matches.size(); i++) {
1287 nick = (String) matches.get(i);
1288 hint += nick + ", ";
1289 for (int j = 1; j <= nick.length() - nickPartLen; j++) {
1290 if (!nickPartNew.regionMatches(true, nickPartLen,
1291 nick, nickPartLen, j)) {
1292 nickPartNew = nickPartNew.substring(0,
1293 nickPartLen + j - 1);
1294 break;
1295 }
1296 }
1297 }
1298 if (nickPart.length() != nickPartNew.length()) {
1299 nickPart = nickPartNew;
1300 }
1301 // emphasize differense in matches by bold and append hint
1302 // to the conversationArea
1303 // hint = hint.replaceAll() can't be used here because of
1304 // its case sensitive nature
1305 //Pattern pattern = Pattern.compile("(" + nickPart
1306 // + ")([^,]+), ", Pattern.CASE_INSENSITIVE);
1307 //hint = pattern.matcher(hint).replaceAll("$1<b>$2</b>, ");
1308 conversationArea.append(hint.substring(0, hint.length() - 2) + "\n", ConversationArea.RECEIVER, true);
1309 } else {
1310 nickPart = (String) matches.firstElement();
1311 if (startPosition == 0)
1312 append = ": ";
1313 else
1314 append = " ";
1315 }
1316
1317 String newText = text.substring(0, startPosition);
1318 newText += nickPart + append;
1319 newText += text.substring(caretPosition);
1320 textEntryArea.setText(newText);
1321 /* Set caret to the appropriate position */
1322 textEntryArea.setCaretPosition(startPosition
1323 + nickPart.length() + append.length());
1324 }
1325
1326 } /* end of the lazy "if" */
1327 }
1328
1329 /**
1330 * Joins the chatroom and adds this chatroomwindow to the TabFrame
1331 *
1332 * @author Adam Olsen
1333 * @version 1.0
1334 */
1335 class JoinChatThread extends Thread {
1336 private String errorMessage;
1337
1338 private boolean cancelled = false;
1339
1340
1341 public void cancel() {
1342 interrupt();
1343 cancelled = true;
1344 }
1345
1346 public void run() {
1347 chat.addMessageListener(messageListener);
1348 chat.addParticipantListener(participantListener);
1349 chat.addSubjectUpdatedListener(subjectListener);
1350 chat.addParticipantStatusListener(statusListener);
1351 chat.addUserStatusListener(userStatusListener);
1352 chat.addInvitationRejectionListener(invitationRejectionPacketListener);
1353
1354 int errorCode = 0;
1355
1356 try {
1357 chat.join(nickname, pass, new DiscussionHistory(),
1358 SmackConfiguration.getPacketReplyTimeout());
1359 } catch (XMPPException e) {
1360 if (!cancelled) {
1361 if (e.getXMPPError() == null)
1362 {
1363 errorMessage = e.getMessage();
1364
1365 }
1366 else {
1367 errorMessage = resources.getString("xmppError"
1368 + e.getXMPPError().getCode());
1369 errorCode = e.getXMPPError().getCode();
1370 }
1371
1372
1373 }
1374 }
1375
1376 final int tempError = errorCode;
1377 if (!cancelled) {
1378 SwingUtilities.invokeLater(new Runnable() {
1379 public void run() {
1380 if (errorMessage != null) {
1381 if(tempError == 409 && joins++ < 1 && !removed)
1382 {
1383 nickname += " ";
1384 leaveChat();
1385 ChatRoomPanel window = new ChatRoomPanel(chatroom, nickname, pass);
1386 BuddyList.getInstance().removeTabPanel(ChatRoomPanel.this);
1387 window.startChat();
1388
1389
1390 return;
1391 }
1392
1393 serverErrorMessage(errorMessage);
1394 } else {
1395 if (cancelled) {
1396 errorMessage = "error";
1397 } else {
1398 //set up a packet to be sent to my user in
1399 // every groupchat
1400 Presence presence = new Presence(
1401 Presence.Type.AVAILABLE, BuddyList
1402 .getInstance()
1403 .getCurrentStatusString(), 0,
1404 BuddyList.getInstance()
1405 .getCurrentPresenceMode());
1406 presence.setTo(getRoomName() + '/'
1407 + getNickname());
1408
1409 BuddyList.getInstance().getConnection()
1410 .sendPacket(presence);
1411 }
1412 }
1413 }
1414 });
1415 } else {
1416 errorMessage = "cancelled";
1417 }
1418
1419 if (errorMessage != null) {
1420 try {
1421 Thread.sleep(1000);
1422 leaveChat();
1423 } catch (Exception neverCaught) {
1424 }
1425 }
1426 }
1427 }
1428 }