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") + ":&nbsp;&nbsp;</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>&nbsp;&nbsp;"
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    }