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.*;
025    import java.text.SimpleDateFormat;
026    import java.util.*;
027    import java.util.regex.Pattern;
028    import javax.swing.*;
029    import javax.swing.event.*;
030    import javax.swing.text.html.HTMLDocument;
031    import org.jivesoftware.smack.*;
032    import org.jivesoftware.smack.packet.*;
033    import com.valhalla.gui.*;
034    import com.valhalla.jbother.*;
035    import com.valhalla.jbother.jabber.BuddyStatus;
036    import com.valhalla.jbother.jabber.smack.*;
037    import com.valhalla.settings.Settings;
038    
039    /**
040     * This is the panel that contains a groupchat conversation.  It is placed in a JTabbedPane in
041     * GroupChat frame.
042     *
043     * @author Adam Olsen
044     **/
045    public class ChatRoomPanel extends JPanel implements LogViewerCaller, TabFramePanel
046    {
047            private ResourceBundle resources = ResourceBundle.getBundle( "JBotherBundle", Locale.getDefault() );
048            private JTextField textEntryArea = new JTextField( 60 );
049    
050            private StringBuffer conversationText = new StringBuffer();
051            private JScrollPane conversationScroll;
052            private ConversationArea conversationArea = new ConversationArea();
053    
054            private JMenuItem
055                                                    clearItem = new JMenuItem( resources.getString( "clear" ) ),
056                                                    logItem = new JMenuItem( resources.getString( "viewLog" ) ),
057                                                    newItem = new JMenuItem( resources.getString( "joinRoom" ) ),
058                                                    leaveItem = new JMenuItem( resources.getString( "leaveRoom" ) ),
059                                                    topicItem = new JMenuItem( resources.getString( "setRoomSubject" ) ),
060                                                    nickItem = new JMenuItem( resources.getString( "changeNickname" ) );
061    
062            private JButton topicButton = new JButton( resources.getString( "changeButton" ) );
063            private JPopupMenu popMenu = new JPopupMenu();
064    
065            private int oldMaximum = 0;
066            private HTMLDocument document = (HTMLDocument)conversationArea.getDocument();
067            private JPanel scrollPanel = new JPanel( new GridLayout( 1, 0 ) );
068            private JSplitPane mainPanel = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT );
069            private JSplitPane container;
070            private GroupChat chat;
071            private String chatroom, nickname;
072            private Hashtable buddyStatuses = new Hashtable();
073            private GroupChatNickList nickList;
074            private WaitDialog wait = new WaitDialog( resources.getString( "pleaseWait" ), resources.getString( "attemptingToJoin" ) );
075            private String subject = resources.getString( "noSubject" );
076            private JLabel subjectLabel = new JLabel();
077            private int countOfNewMessages = 0;
078            // for logging
079            private File logFile;
080            private PrintWriter logOut;
081            private FileWriter fw;
082            private String currentNick;
083            private ChatRoomPanel thisPointer = this;
084            private boolean listenersAdded = false;
085    
086            /**
087             * This sets up the appearance of the chatroom window
088             * @param chatroom the chatroom address
089             * @param nickname the nickname to use when joining
090             **/
091            public ChatRoomPanel( String chatroom, String nickname )
092            {
093                    this.chatroom = chatroom;
094                    this.nickname = nickname;
095    
096                    BuddyList.getInstance().startTabFrame();
097    
098                    setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );
099                    setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) );
100    
101                    JPanel subjectPanel = new JPanel();
102                    subjectPanel.setLayout( new BoxLayout( subjectPanel, BoxLayout.Y_AXIS ) );
103                    subjectPanel.add( subjectLabel );
104                    subjectPanel.add( Box.createHorizontalGlue() );
105                    add( subjectPanel );
106                    add( Box.createRigidArea( new Dimension( 0, 5 ) ) );
107    
108                    add( mainPanel );
109    
110                    nickList = new GroupChatNickList( this );
111    
112                    String divLocString = Settings.getInstance().getProperty( "chatWindowDividerLocation" );
113                    int divLoc = 0;
114    
115                    if( divLocString != null )
116                    {
117                            divLoc = Integer.parseInt( divLocString );
118                    }
119                    else {
120                            Dimension dimension = BuddyList.getInstance().getTabFrame().getSize();
121                            divLoc = (int)dimension.getWidth() - 127;
122                    }
123    
124                    mainPanel.setDividerLocation( divLoc );
125                    mainPanel.setOneTouchExpandable( true );
126                    mainPanel.setResizeWeight( 1 );
127                    mainPanel.addPropertyChangeListener( "lastDividerLocation", new DividerListener() );
128    
129                    conversationScroll = new JScrollPane( conversationArea );
130                    conversationScroll.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS );
131                    conversationArea.setScroll( conversationScroll );
132    
133                    setUpPopMenu();
134    
135                    scrollPanel.add( conversationScroll );
136    
137                    JPanel containerPanel = new JPanel();
138                    GridBagLayout grid = new GridBagLayout();
139                    GridBagConstraints c = new GridBagConstraints();
140    
141                    c.gridx = 0;
142                    c.gridy = 0;
143                    c.fill = GridBagConstraints.BOTH;
144                    c.weightx = 1;
145                    c.weighty = 1;
146    
147                    c.gridwidth = 1;
148    
149                    grid.setConstraints( scrollPanel, c );
150                    containerPanel.setLayout( grid );
151                    containerPanel.add( scrollPanel );
152    
153                    c.gridy++;
154                    c.weightx = 1;
155                    c.weighty = 0;
156                    c.fill = GridBagConstraints.HORIZONTAL;
157    
158                    Component sep = Box.createRigidArea( new Dimension( 0, 5 ) );
159                    grid.setConstraints( sep, c );
160                    containerPanel.add( sep );
161    
162                    c.gridy++;
163    
164                    textEntryArea.setBorder( BorderFactory.createEtchedBorder() );
165                    grid.setConstraints( textEntryArea, c );
166    
167                    containerPanel.add( textEntryArea );
168    
169                    mainPanel.add( containerPanel );
170                    mainPanel.add( nickList );
171    
172                    textEntryArea.grabFocus();
173            textEntryArea.setFocusTraversalKeysEnabled(false); //for disable focus traversal with TAB
174                    setSubject( subject );
175    
176                    addListeners();
177            }
178    
179            /**
180             * @return true if the TabFrame panel listeners have already been added to this panel
181            */
182            public boolean listenersAdded() { return listenersAdded; }
183    
184            /**
185             * Sets whether or not the TabFrame panel listeners have been added
186             * @param added true if they have been added
187            */
188            public void setListenersAdded( boolean added ) { this.listenersAdded = added; }
189    
190            /**
191             * @return the input area of this panel
192            */
193            public Component getInputComponent() { return textEntryArea; }
194    
195            /**
196             * @return the JList representing the nicklist
197            */
198            public JList getNickList() { return nickList.getList(); }
199    
200            /**
201             * @return the text entry area
202            */
203            public JTextField getTextEntryArea() { return textEntryArea; }
204    
205            /**
206             * Listens for a change in the divider location, and saves it for later retreival
207             * @author Adam Olsen
208             * @version 1.0
209            */
210            private class DividerListener implements PropertyChangeListener
211            {
212                    public void propertyChange( PropertyChangeEvent e )
213                    {
214                            Settings.getInstance().setProperty( "chatWindowDividerLocation", e.getOldValue().toString() );
215                            BuddyList.getInstance().getTabFrame().saveStates();
216                    }
217            }
218    
219            /**
220             * Updates the font in the ConversationArea
221             * @param font the font to update to
222            */
223            public void updateStyle( Font font ) { conversationArea.loadStyleSheet( font ); }
224    
225            /**
226             * Look for a right click, and show a pop up menu
227             * @author Adam Olsen
228             * @version 1.0
229            **/
230            class RightClickListener extends MouseAdapter
231            {
232                    public void mousePressed( MouseEvent e ) { checkPop( e ); }
233                    public void mouseReleased( MouseEvent e ) { checkPop( e ); }
234                    public void mouseClicked( MouseEvent e ) { checkPop( e ); }
235    
236                    public void checkPop( MouseEvent e )
237                    {
238                            // look for the popup trigger.. usually a right click
239                            if( e.isPopupTrigger() )
240                            {
241                                    popMenu.show( e.getComponent(), e.getX(), e.getY() );
242                            }
243                    }
244            }
245    
246            /**
247             * Add the various menu items to the popup menu
248            **/
249            private void setUpPopMenu()
250            {
251                    MenuItemListener listener = new MenuItemListener();
252    
253                    conversationArea.addMouseListener( new RightClickListener() );
254    
255                    popMenu.add( clearItem );
256                    popMenu.add( topicItem );
257                    popMenu.add( nickItem );
258                    popMenu.addSeparator();
259                    popMenu.add( logItem );
260    
261                    popMenu.addSeparator();
262                    popMenu.add( newItem );
263                    popMenu.add( leaveItem );
264    
265                    topicButton.addActionListener( listener );
266                    clearItem.addActionListener( listener );
267                    logItem.addActionListener( listener );
268                    newItem.addActionListener( listener );
269                    leaveItem.addActionListener( listener );
270                    topicItem.addActionListener( listener );
271                    nickItem.addActionListener( listener );
272            }
273    
274            /**
275             * Listens for items to be selected in the menu
276             * @author Adam Olsen
277             * @version 1.0
278            */
279            private class MenuItemListener implements ActionListener
280            {
281                    public void actionPerformed( ActionEvent e )
282                    {
283                            if( e.getSource() == nickItem ) changeNickHandler();
284                            if( e.getSource() == leaveItem )
285                            {
286                                    BuddyList.getInstance().getTabFrame().removePanel( thisPointer );
287                                    BuddyList.getInstance().stopTabFrame();
288                            }
289                            if( e.getSource() == topicItem || e.getSource() == topicButton ) topicHandler();
290                            if( e.getSource() == clearItem ) conversationArea.setText( "" );
291                            if( e.getSource() == newItem )
292                            {
293                                    GroupChatBookmarks gc = new GroupChatBookmarks();
294                                    gc.show();
295                                    gc.toFront();
296                            }
297    
298                            if( e.getSource() == logItem ) new LogViewerDialog( thisPointer, getRoomName() );
299                    }
300            }
301    
302            /**
303             * Opens the log window for this chat room
304            */
305            public void openLogWindow()
306            {
307                    new LogViewerDialog( this, getRoomName() );
308            }
309    
310            /**
311             * Adds a buddy to the nickname list
312             * @param buddy the buddy to add
313            */
314            public void addBuddy( String buddy ) { nickList.addBuddy( buddy ); }
315    
316            /**
317             * Removes a buddy from the nick list
318             * @param buddy the buddy to remove
319            */
320            public void removeBuddy( String buddy )
321            {
322                    nickList.removeBuddy( buddy );
323                    buddyStatuses.remove( buddy );
324            }
325    
326            /**
327             * Opens the log file
328            */
329            public void startLog()
330            {
331                    // for loggingg
332                    if( Settings.getInstance().getBoolean( "keepLogs" ) )
333                    {
334                            try {
335                                    String logFileName = LogViewerDialog.getDateName() + ".log.html";
336                                    String logFileDir = JBother.profileDir + File.separatorChar +
337                                            "logs" + File.separatorChar + getRoomName().replaceAll( "@", "_at_" ).replaceAll( "\\/", "-" );
338    
339                                    File logDir = new File( logFileDir );
340    
341                                    if( !logDir.isDirectory() && !logDir.mkdirs() ) Standard.warningMessage( this, resources.getString( "log" ),
342                                            resources.getString( "couldNotCreateLogDir" ) );
343                                    logFile = new File( logDir, logFileName );
344                                    fw = new FileWriter( logFile, true );
345                                    logOut = new PrintWriter( fw, true );
346                            }
347                            catch( IOException e ) { com.valhalla.Logger.debug( "Could not open logfile." ); }
348                    }
349            }
350    
351            /**
352             * Gets the BuddyStatus represending a user in the room
353             * @param user the BuddyStatus to get
354             * @return the requested BuddyStatus
355            */
356            public BuddyStatus getBuddyStatus( String user )
357            {
358                    if( !buddyStatuses.containsKey( user ) )
359                    {
360                            BuddyStatus buddy = new BuddyStatus( user );
361                            buddy.setName( user.substring( user.indexOf( "/" ) + 1, user.length() ) );
362                            buddyStatuses.put( user, buddy );
363                    }
364    
365                    return (BuddyStatus)buddyStatuses.get( user );
366            }
367    
368            /**
369             * Returns the tab name for the TabFramePanel
370             * @return the panel name
371            */
372            public String getPanelName() { return getShortRoomName(); }
373    
374            /**
375             * Returns the tooltip for the tab in the TabFrame
376             * @return the tooltip for this tab in the tab frame
377            */
378            public String getTooltip() { return getRoomName(); }
379    
380            /**
381             * Returns the window title
382             * @return the window title for the TabFrame when this tab is selected
383            */
384            public String getWindowTitle() { return resources.getString( "groupChat" ) + ": " + getRoomName(); }
385    
386            /**
387             * Gets the short room name - for example, if you are talking in
388             * jdev@conference.jabber.org, it would return "jdev"
389             * @return short room name
390             **/
391            public String getShortRoomName()
392            {
393                    return chat.getRoom().replaceAll( "\\@.*", "" );
394            }
395    
396            /**
397             * Gets the entire room name, server included
398             * @return gets the room address
399             **/
400            public String getRoomName()
401            {
402                    return chat.getRoom();
403            }
404    
405            /**
406             * Starts the groupchat.  Sets up a thread to connect, and start that thread
407             **/
408            public void startChat()
409            {
410                    if( !BuddyList.getInstance().checkConnection() ) return;
411    
412                    chat = new GroupChat( BuddyList.getInstance().getConnection(), chatroom );
413    
414                    //set up the presence and message listeners so that they are
415                    //ready from the momemnt we start the chat.
416                    chat.addMessageListener( new GroupChatMessagePacketListener( this ) );
417                    chat.addParticipantListener( new GroupParticipantListener( this ) );
418                    wait.setVisible( true );
419    
420                    Thread thread = new Thread( new JoinChatThread() );
421                    thread.start();
422    
423                    startLog();
424            }
425    
426            /**
427             * Asks for a new topic, then sets the new topic
428            */
429            private void topicHandler()
430            {
431                    //find out what they want the subject to be
432                    String result = (String)JOptionPane.showInputDialog( null,
433                            resources.getString( "enterSubject" ),
434                            resources.getString( "setSubject" ), JOptionPane.QUESTION_MESSAGE, null, null, subject );
435    
436                    if( result != null && !result.equals( "" ) )
437                    {
438                            //send a packet to the server with a subject and an empty body
439                            Message packet = new Message( getRoomName(), Message.Type.GROUP_CHAT );
440                            packet.setSubject( result );
441                            packet.setBody( "/me " + resources.getString( "hasSetTopic" ) + " " + result );
442    
443                            if( !BuddyList.getInstance().checkConnection() )
444                            {
445                                    BuddyList.getInstance().connectionError();
446                                    return;
447                            }
448                            else {
449                                    BuddyList.getInstance().getConnection().sendPacket( packet );
450                            }
451    
452                            setSubject( result );
453                    }
454            }
455    
456            /**
457             * Asks for a new nickname, and sends a nickname change request
458            */
459            private void changeNickHandler()
460            {
461                    if( currentNick == null ) currentNick = chat.getNickname();
462                    String result = (String)JOptionPane.showInputDialog( null,
463                            resources.getString( "enterNickname" ),
464                            resources.getString( "setNickname" ), JOptionPane.QUESTION_MESSAGE, null, null, currentNick );
465    
466                    if( result != null && !result.equals( "" ) )
467                    {
468                            //send a packet to the server with a subject and an empty body
469                            Presence packet = new Presence( Presence.Type.AVAILABLE, BuddyList.getInstance().getCurrentStatusString(), 0, BuddyList.getInstance().getCurrentPresenceMode() );
470                            packet.setTo( getRoomName() + '/' + result );
471                            packet.setFrom( getRoomName() + '/' + currentNick );
472    
473                            if( !BuddyList.getInstance().checkConnection() )
474                            {
475                                    BuddyList.getInstance().connectionError();
476                                    return;
477                            }
478                            else {
479                                    BuddyList.getInstance().getConnection().sendPacket( packet );
480                            }
481    
482                            currentNick = result;
483                    }
484            }
485    
486    
487            /**
488             * Adds the event listeners for the various components in this chatwindows
489             **/
490            public void addListeners()
491            {
492                    //set up the window so you can press enter in the text box and
493                    //that will send the message.
494                    Action SendMessageAction = new AbstractAction()
495                    {
496                            public void actionPerformed( ActionEvent e )
497                            {
498                                    sendHandler();
499                            }
500                    };
501    
502                    Action nickCompletionAction = new AbstractAction()
503                    {
504                            public void actionPerformed( ActionEvent e )
505                            {
506                                    nickCompletionHandler();
507                            }
508                    };
509    
510                    //set it up so that if they drag in the conversation window, it grabs the focus
511                    conversationArea.addMouseMotionListener( new MouseMotionAdapter()
512                    {
513                            public void mouseDragged( MouseEvent e )
514                            {
515                                    conversationArea.grabFocus();
516                            }
517                    } );
518    
519                    //set it up so that if there isn't any selected text in the conversation area
520                    //the textentryarea grabs the focus.
521                    conversationArea.addFocusListener( new FocusListener()
522                    {
523                            public void focusGained( FocusEvent e )
524                            {
525                                    if( conversationArea.getSelectedText() == null )
526                                    {
527                                            textEntryArea.requestFocus();
528                                    }
529                                    else conversationArea.grabFocus();
530                            }
531    
532                            public void focusLost( FocusEvent e ) { }
533                    } );
534    
535                    Action closeAction = new AbstractAction()
536                    {
537                            public void actionPerformed( ActionEvent e )
538                            {
539                                    BuddyList.getInstance().getTabFrame().removePanel( thisPointer );
540                                    BuddyList.getInstance().stopTabFrame();
541                            }
542                    };
543    
544                    KeyStroke enterStroke = KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 );
545                    textEntryArea.getInputMap().put( enterStroke, SendMessageAction );
546    
547                    KeyStroke tabStroke = KeyStroke.getKeyStroke( KeyEvent.VK_TAB, 0 );
548                    textEntryArea.getInputMap().put( tabStroke, nickCompletionAction );
549    
550                    textEntryArea.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), closeAction );
551            }
552    
553            /**
554             * Leaves this room and removes it from the groupchat frame
555            */
556            public void leave()
557            {
558                    closeLog();
559                    try {
560                            chat.leave();
561                    }
562                    catch( IllegalStateException e ) { }
563            }       //leave the chatroom
564    
565            /**
566             * Focuses the text entry area
567            */
568            public void focusYourself()
569            {
570                    conversationArea.moveToEnd();
571                    textEntryArea.grabFocus();
572    
573                    countOfNewMessages = 0;
574                    JTabbedPane tabPane = BuddyList.getInstance().getTabFrame().getTabPane();
575                    tabPane.setTitleAt(tabPane.indexOfComponent(this),this.getShortRoomName());
576            }
577    
578            /**
579             * Gets the nickname currently being used in the chat room
580             * @return the nickname being used in the chatroom
581            */
582            public String getNickname()
583            {
584                    if( currentNick == null ) currentNick = chat.getNickname();
585                    return currentNick;
586            }
587    
588            /**
589             * Recieves a message
590             * @param from who it's from
591             * @param message the message
592            */
593            public void recieveMessage( String from, String message )
594            {
595                    message = ConversationText.replaceText( message, false ); //replace emoticons, links, etc...
596                    boolean messageToMe = false;
597                    if( from.equals( "" ) || from.toLowerCase().equals( chat.getRoom().toLowerCase() ) )
598                    {
599                            //server message
600                            conversationArea.append( "<div>"+getDate() + "<font color='green'> -> " + message + "</font></div>" );
601                    }
602                    else {
603    
604                            boolean highLightedSound = false;
605    
606                            if( message.startsWith( " /me " ) )
607                            {
608                                    message = message.replaceAll( "^ \\/me ", "" );
609                                    conversationArea.append( "<div>"+getDate() +
610                                            " <b><font color='maroon'>* " + from + "</font></b> " + message + "</div>" );
611                            }
612                            else if( message.replaceAll( "<[^>]*>", "" ).matches( ".*(^|\\W)" + nickname + "\\W.*" ) )
613                            {
614                                    messageToMe = true;
615                                    conversationArea.append( "<div style='background-color: #FFC4C4;'>"+getDate() + " <b><font color='#16569e'>" + from + "</font></b>: " + message + "</div>" );
616                                    com.valhalla.jbother.sound.SoundPlayer.play( "groupHighlightedSound" );
617                                    highLightedSound = true;
618                            }
619                            else {
620                                    conversationArea.append( "<div>"+getDate() + " <font color='#16569e'><b>" +
621                                            from + "</b></font>: " + message + "</div>" );
622                            }
623    
624                            if( !highLightedSound ) com.valhalla.jbother.sound.SoundPlayer.play( "groupRecievedSound" );
625                    }
626    
627                    try {
628                            JTabbedPane tabPane = BuddyList.getInstance().getTabFrame().getTabPane();
629                            if (tabPane.getSelectedComponent() != this) {
630                                    countOfNewMessages++;
631                                    String title = this.getShortRoomName()+" ("+countOfNewMessages+")";
632                                    if (messageToMe) title = "*"+title;
633                                    tabPane.setTitleAt(tabPane.indexOfComponent(this),title);
634                            }
635                    }
636                    catch( Exception ex ) { }
637    
638                    if( logOut != null )
639                    {
640                            logOut.println( conversationArea.getLastHTML() );
641                    }
642            }
643    
644            /**
645             * Closes the log file
646            */
647            public void closeLog()
648            {
649                    if( fw != null )
650                    {
651                            try {
652                                    fw.close();
653                            }
654                            catch( IOException e ) { }
655                    }
656            }
657    
658            /**
659             * @return a String representing the current time the format: [Hour:Minute:Second]
660            */
661            public String getDate()
662            {
663                    SimpleDateFormat formatter = new SimpleDateFormat( "[HH:mm:ss]" );
664                    String date = formatter.format( new Date() );
665                    return date;
666            }
667    
668            /**
669             * Sets the subject of the room
670             * @param subject the subject to set
671            */
672            public void setSubject( String subject )
673            {
674                    this.subject = subject;
675                    if( BuddyList.getInstance().getTabFrame() != null )
676                            BuddyList.getInstance().getTabFrame().setSubject( this );
677    
678                    subjectLabel.setText( "<html><b>" + resources.getString( "subject" ) + ": </b>" + subject + "</html>" );
679            }
680    
681            /**
682             * Returns the current room subject
683             * @return the current room subject
684            */
685            public String getSubject() { return this.subject; }
686    
687            /**
688             * Gets all the buddy statuses in the room
689             * @return all BuddyStatuses
690            */
691            public Hashtable getBuddyStatuses() { return this.buddyStatuses; }
692    
693            /**
694             * Sends the message currently in the textentryarea
695            */
696            private void sendHandler()
697            {
698                    String text = textEntryArea.getText();
699    
700                    Message message = new Message();
701                    message.setTo( getRoomName() );
702                    message.setBody( text );
703                    message.setType( Message.Type.GROUP_CHAT );
704    
705                    if( !textEntryArea.getText().equals( "" ) )
706                    {
707                            try {
708                                    chat.sendMessage( message );
709                            }
710                            catch( XMPPException e )
711                            {
712                                    com.valhalla.Logger.debug( "Could not send message." );
713                            }
714    
715                            textEntryArea.setText( "" );
716                    }
717            }
718    
719            /**
720             * Implementation of Tab nick completion in the textEntryArea
721            */
722            private void nickCompletionHandler()
723            {
724                    String text = textEntryArea.getText();
725    
726                    /* if we have nothing => do nothing */
727                    if( !text.equals( "" ) ) {
728                            int caretPosition   = textEntryArea.getCaretPosition();
729                            int startPosition   = text.lastIndexOf(" ", caretPosition - 1) + 1;
730                            String nickPart     = text.substring(startPosition, caretPosition);
731                            Vector matches      = new Vector();
732    
733                            java.util.List keys = new ArrayList( buddyStatuses.keySet() );
734                            Iterator iterator   = keys.iterator();
735    
736                            while( iterator.hasNext() ) {
737                                    BuddyStatus buddy = (BuddyStatus)buddyStatuses.get(iterator.next());
738                                    try {
739                                            String nick = buddy.getUser().substring(buddy.getUser().lastIndexOf("/")+1);
740                                            if (nick.toLowerCase().startsWith(nickPart.toLowerCase())) {
741                                                    matches.add(nick);
742                                            }
743                                    } catch(java.lang.NullPointerException e) {
744                                    }
745                            }
746    
747                            if (matches.size() > 0) {
748                                    String append = "";
749    
750                                    if (matches.size() > 1) {
751                                            String nickPartNew = (String)matches.firstElement();
752                                            String nick = "";
753                                            String hint   = nickPartNew + ", ";
754                                            int nickPartLen = nickPart.length();
755                                            for( int i = 1; i < matches.size(); i++ ){
756                                                    nick = (String)matches.get(i);
757                                                    hint += nick + ", ";
758                                                    for( int j = 1; j <= nick.length()-nickPartLen; j++ ){
759                                                            if (!nickPartNew.regionMatches(true,nickPartLen,nick,nickPartLen,j)){
760                                                                    nickPartNew = nickPartNew.substring(0, nickPartLen+j-1);
761                                                                    break;
762                                                            }
763                                                    }
764                                            }
765                                            if (nickPart.length() != nickPartNew.length()) {
766                                                    nickPart = nickPartNew;
767                                            }
768                                            // emphasize differense in matches by bold and append hint to the conversationArea
769                                            // hint = hint.replaceAll() can't be used here because of its case sensitive nature
770                                            Pattern pattern = Pattern.compile("(" + nickPart + ")([^,]+), ", Pattern.CASE_INSENSITIVE);
771                                            hint = pattern.matcher(hint).replaceAll("$1<b>$2</b>, ");
772                                            conversationArea.append( "<div style=\"color: #16569e;\">" + hint.substring(0,hint.length()-2) + "</div>" );
773                                    } else {
774                                            nickPart = (String)matches.firstElement();
775                                            if( startPosition == 0 ) append = ": ";
776                                            else                     append = " ";
777                                    }
778    
779                                    String newText = text.substring( 0, startPosition );
780                                    newText += nickPart + append;
781                                    newText += text.substring(caretPosition);
782                                    textEntryArea.setText( newText );
783                                    /* Set caret to the appropriate position */
784                                    textEntryArea.setCaretPosition(startPosition + nickPart.length() + append.length());
785                            }
786    
787                    } /* end of the lazy "if" */
788            }
789    
790            /**
791             * Joins the chatroom and adds this chatroomwindow to the TabFrame
792             * @author Adam Olsen
793             * @version 1.0
794            */
795            class JoinChatThread implements Runnable
796            {
797                    private String errorMessage;
798    
799                    public void run()
800                    {
801                            try {
802                                    chat.join( nickname, 30500 );
803                            }
804                            catch( XMPPException e )
805                            {
806                                    if( e.getXMPPError() != null ) errorMessage = e.getXMPPError().getMessage();
807                                    else errorMessage = e.getMessage();
808                                    if( errorMessage == null ) errorMessage = resources.getString( "unknownError" );
809                            }
810    
811                            SwingUtilities.invokeLater( new Runnable()
812                            {
813                                    public void run()
814                                    {
815                                            wait.dispose();
816                                            if( errorMessage != null )
817                                            {
818                                                    Standard.warningMessage( thisPointer, resources.getString( "joinChat" ), errorMessage );
819                                                    chat.leave();
820                                                    new GroupChatBookmarks().setVisible( true );
821                                                    BuddyList.getInstance().stopTabFrame();
822                                            }
823                                            else {
824                                                    BuddyList.getInstance().addTabPanel( thisPointer );
825                                            }
826                                    }
827                            } );
828                    }
829            }
830    }