/***************************************************************************
                          chatwindow.cpp  -  description
                             -------------------
    begin                : Wed Jan 15 22:41:32 CST 2003
    copyright            : (C) 2003 by Mike K. Bennett
    email                : mkb137b@hotmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "chatwindow.h"

#include <qdir.h>
#include <qfile.h>
#include <qregexp.h>
#include <qtextedit.h>

#include <kaction.h>
#include <kcolordialog.h>
#include <kdebug.h>
#include <kfiledialog.h>
#include <kfontdialog.h>
#include <klocale.h>
#include <knotifyclient.h>
#include <kpopupmenu.h>
#include <ksqueezedtextlabel.h>
#include <ktextbrowser.h>
#include <ktoolbarbutton.h>

#include "../actions/accountaction.h"
#include "../contact/contact.h"
#include "../contact/contactlist.h"
#include "../network/chatinformation.h"
#include "../network/msnswitchboardconnection.h"
#include "../currentaccount.h"
#include "../kmessdebug.h"
#include "../specialgroups.h"
#include "../kmessapplication.h"
#include "chatview.h"
#include "contactaction.h"
#include "contactsidebar.h"

#ifdef KMESSDEBUG_CHATWINDOW
  #define KMESSDEBUG_CHATWINDOW_GENERAL
  #define KMESSDEBUG_CHATWINDOW_FILETRANSFER
#endif

// The constructor
ChatWindow::ChatWindow(QWidget *parent, const char *name)
 : ChatWindowInterface(parent, name),
   chatView_(0),
   contactDockWidget_(0),
   contactList_(0),
   contactSidebar_(0),
   currentAccount_(0),
   firstMessage_(true),
   initialized_(false),
   mainDockWidget_(0),
   msnSwitchboardConnection_(0),
   sidebarHeight_(170),
   sidebarWidth_(150)
{
  setWFlags(getWFlags() | WDestructiveClose);  // Closing the window destroyes the object too
  caption_ = i18n("Chat");
  setCaption( caption_ );
  // Connect the blink timer
  connect( &blinkTimer_, SIGNAL(      timeout() ),
           this,         SLOT  ( blinkCaption() ) );
  // Connect the typing timer
  connect( &typingTimer_,  SIGNAL(     timeout() ),
           this,           SLOT  (  stopTyping() ) );
}



// The destructor
ChatWindow::~ChatWindow()
{
#ifdef KMESSDEBUG_KMESS
  kdDebug() << "ChatWindow destructor: closing window..." << endl;
#endif

  // Saving properties manually.
  saveProperties( kapp->config() );

  // Delete the child objects
  delete chatView_;
  delete contactSidebar_;
  delete contactDockWidget_;
  delete mainDockWidget_;

  if(msnSwitchboardConnection_ != 0)
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow destructor: switchboard is still active, allowing switchboard to close nicely" << endl;
#endif

    // Let the switchboard connection delete itself when it's ready
    msnSwitchboardConnection_->closeConnectionLater(true);
    msnSwitchboardConnection_ = 0;
  }

  // Delete the contact actions
  contactActions_.setAutoDelete(true);
  contactActions_.clear();

#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "DESTROYED ChatWindow " << endl;
#endif
}



// Make the caption blink if the window still doesn't have focus
void ChatWindow::blinkCaption()
{
  // If the window now has focus...
  if ( isActiveWindow() /*hasMouse()*/ )
  {
    // Stop the timer
    blinkTimer_.stop();
    // Reset the caption
    setCaption( caption_ );
  }
  else
  {
    // Make the caption alternate case
    if ( blinkToUpper_ )
    {
      setCaption( caption_.upper() );
    }
    else
    {
      setCaption( caption_.lower() );
    }
    blinkToUpper_ = !blinkToUpper_;
  }
}



// Check if the message is the first message and if so, pop up a window with the text.
void ChatWindow::checkFirstMessage(const QString& handle, const QString& text)
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - First message is " << firstMessage_ << endl;
#endif

  // If this is the first message received...
  if ( firstMessage_ )
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow - Show the window." << endl;
#endif
    firstMessage_ = false;

    // Show the window minimized
    showMinimized();
    checkSidebarPreferences();


    // Check if we should autoreply the application message
    if( currentAccount_           != 0 &&
        msnSwitchboardConnection_ != 0 &&
        currentAccount_->getAutoreply()   )
    {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
      kdDebug() << "ChatWindow - Sending auto reply to application message." << endl;
#endif
      // Send the autoreply message
      QString awayMessage = currentAccount_->getAutoreplyMessage();
      awayMessage += i18n(" (This message was sent automatically)");
      msnSwitchboardConnection_->sendChatMessage( awayMessage );

      // Show the message in the browser window
      chatView_->showAppMessage( text );
    }


    // Emit the new message signal so that a balloon will popup
    if ( contactList_ != 0 )
    {
      Contact *contact = contactList_->getContactByHandle( handle );
      if ( contact != 0 )
      {
        emit newChat( contact, text, this );
      }
    }
  }
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  else
  {
    kdDebug() << "ChatWindow - Not the first message.  Don't show the window." << endl;
  }
#endif
}



// Toggle the sidebar based on account preferences
void ChatWindow::checkSidebarPreferences()
{
  // If the contact dock widget is visible...
  if (    ( (  contactDockWidget_->isVisible() ) && ( !currentAccount_->getShowSidebar() ) )
       || ( ( !contactDockWidget_->isVisible() ) && (  currentAccount_->getShowSidebar() ) ) )
  {
    contactDockWidget_->changeHideShowState();
  }
}



// A contact joined the chat
void ChatWindow::contactJoined(QString handle, QString friendlyName)
{
  ContactAction *contactAction;

  if ( msnSwitchboardConnection_ != 0 )
  {
    caption_ = msnSwitchboardConnection_->getCaption();
    setCaption( caption_ );
  }

  contactAction = getContactActionByHandle( handle );
  if ( contactAction != 0 )
  {
    contactAction->setInChat( true );
  }

  if ( ( !contactDockWidget_->isVisible() ) && ( !currentAccount_->getShowSidebar() ) )
  {
    QString message;
    message = i18n("%1 has joined the chat.").arg( friendlyName );
    chatView_->showSystemMessage( message );
  }
}



// A contact left the chat
void ChatWindow::contactLeft(QString handle)
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow: contact '" << handle << "' has left." << endl;
#endif

  ContactAction *contactAction;

  contactAction = getContactActionByHandle( handle );
  if ( contactAction != 0 )
  {
    contactAction->setInChat( false );
  }

  if ( ( !contactDockWidget_->isVisible() ) && ( !currentAccount_->getShowSidebar() ) )
  {
    QString message;
    message = i18n("%1 has left the chat.").arg( handle );
    chatView_->showSystemMessage( message );
  }

  if ( msnSwitchboardConnection_ != 0 )
  {
    caption_ = msnSwitchboardConnection_->getCaption();
    setCaption( caption_ );

    if(! isVisible() && msnSwitchboardConnection_->isEmpty())
    {
      // MSN7 often initiates hidden chat connections to retrieve
      // display pictures. This code ensures that the hidden chat
      // connection will be closed too.
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
      kdDebug() << "ChatWindow: all contacts left, window is already hidden. close the window." << endl;
#endif
      close(true);
    }
  }
}



// A contact is typing
void ChatWindow::contactTyping(QString, QString friendlyName)
{
  if ( chatView_ != 0 )
  {
    chatView_->typingLabel_->setText( i18n("%1 is typing.").arg( friendlyName ) );
    // Start the timer to disable the label
    typingTimer_.start(5000, true);
  }
}



// Create the contact actions
void ChatWindow::createContactActions(ContactList *contactList)
{
  ContactAction             *contactAction;
  QPtrList<Contact>          contacts( contactList->getContactList() );
  QPtrListIterator<Contact>  it( contacts );
  Contact                   *contact;

  while ( it.current() )
  {
    contact = it.current();
    contactAction = new ContactAction( contact, inviteMenu_ );
    connect( contactAction, SIGNAL(     activated(QString) ),
             this,          SLOT  ( inviteContact(QString) ) );
    contactActions_.append( contactAction );
    ++it;
  }
}



// Create the docking widgets
bool ChatWindow::createDocking()
{
#ifdef KMESSTEST
  ASSERT( chatView_ == 0 );
  ASSERT( contactDockWidget_ == 0 );
  ASSERT( contactSidebar_ ==0 );
  ASSERT( mainDockWidget_ == 0);
#endif

  // Create the main docking widget
  mainDockWidget_ = createDockWidget( "Main dock widget", 0, 0L, "main_dock_widget" );
  if ( mainDockWidget_ == 0 )
  {
    kdDebug() << "ChatWindow - Couldn't create main dock widget." << endl;
    return false;
  }
  connect( mainDockWidget_, SIGNAL(        docking( KDockWidget*, KDockWidget::DockPosition ) ),
           this,            SLOT  ( sidebarDocking( KDockWidget*, KDockWidget::DockPosition ) ) );

  // Create the contact docking widget
  contactDockWidget_ = createDockWidget( "Contact dock widget", 0, 0L, "contact_dock_widget" );
  if ( contactDockWidget_ == 0 )
  {
    kdDebug() << "ChatWindow - Couldn't create contact dock widget." << endl;
    return false;
  }

#ifdef KMESSTEST
  ASSERT( chatView_ == 0 );
  ASSERT( contactDockWidget_ != 0 );
  ASSERT( contactSidebar_ ==0 );
  ASSERT( mainDockWidget_ != 0);
#endif
  return true;
}



// put the marked text/object into the clipboard and remove
//  it from the document
void ChatWindow::editCut()
{
#ifdef KMESSTEST
  ASSERT( chatView_ != 0 );
#endif

  if( chatView_->messageEdit_->hasFocus() )
  {
    chatView_->messageEdit_->cut();
  }
  else if ( chatView_->chatBrowser_->hasFocus() )
  {
    chatView_->chatBrowser_->cut();
  }
}



// put the marked text/object into the clipboard
void ChatWindow::editCopy()
{
#ifdef KMESSTEST
  ASSERT( chatView_ != 0 );
#endif
  if( chatView_->messageEdit_->hasFocus() )
  {
    chatView_->messageEdit_->copy();
  }
  else if ( chatView_->chatBrowser_->hasFocus() )
  {
    chatView_->chatBrowser_->copy();
  }
}



// Bring up a dialog to change the message font color.
void ChatWindow::editFont()
{
#ifdef KMESSTEST
  ASSERT( currentAccount_ != 0 );
#endif
  QFont    selection;
  QString fontFamily;
  int      leftBracket;

  selection = currentAccount_->getFont();

  int result = KFontDialog::getFont( selection, false, this );
  if ( result == KFontDialog::Accepted )
  {
    currentAccount_->setFont( selection );
    fontFamily = selection.family();
    // KDE3 seems to want to throw some stuff in square brackets at the
    //  end of font names.  Strip that stuff off.
    // I'm assuming no real proper font has "[" in the name.
    leftBracket = fontFamily.find( "[" );
    if ( leftBracket >= 0 )
    {
      fontFamily = fontFamily.left( leftBracket );
    }
    fontFamily = fontFamily.stripWhiteSpace();
    selection.setFamily( fontFamily );
    // Set it to the stored account
    currentAccount_->setFont( selection );
  }
}



// Bring up a dialog to change the message font color.
void ChatWindow::editFontColor()
{
#ifdef KMESSTEST
  ASSERT( currentAccount_ != 0 );
#endif
  QColor  color;
  QString fontColor;

  // Show the color dialog
  int result = KColorDialog::getColor( color, this );
  if ( result == KColorDialog::Accepted )
  {
    // Get the string version of the selected color
    fontColor = color.name();
    // Set it to the stored account
    currentAccount_->setFontColor(fontColor);
  }
}



// paste the clipboard into the document
void ChatWindow::editPaste()
{
#ifdef KMESSTEST
  ASSERT( chatView_ != 0 );
#endif
  if( chatView_->messageEdit_->hasFocus() )
  {
    chatView_->messageEdit_->paste();
  }
  else if ( chatView_->chatBrowser_->hasFocus() )
  {
    chatView_->chatBrowser_->paste();
  }
}



// The emoticon button was pressed.
void ChatWindow::emoticonButtonPressed()
{
#ifdef KMESSTEST
  ASSERT( chatView_ != 0 );
#endif
  QPoint point;
  // Get the position of the emoticon button
  point = emoticonButton_->pos();
  // Move the point below the button
  point.setY( point.y() + emoticonButton_->height() );
  // Change it to global coordinates
  point = mapToGlobal( point );
  // Popup the emoticon chooser at that position.
  chatView_->showEmoticonChooser( point );
}



// Return the contact action with the given handle
ContactAction* ChatWindow::getContactActionByHandle(const QString& handle)
{
  ContactAction *contactAction;

  for ( contactAction = contactActions_.first(); contactAction; contactAction = contactActions_.next() )
  {
    if ( contactAction->getHandle() == handle )
    {
      return contactAction;
    }
  }
  return 0;
}



// Returns the switchboard connection: needed by the chatmaster networkwindow code
MsnSwitchboardConnection *ChatWindow::getSwitchboardConnection() const
{
  return msnSwitchboardConnection_;
}



// Initialize the object
bool ChatWindow::initialize()
{
  if ( initialized_ )
  {
    kdDebug() << "ChatWindow already initialized." << endl;
    return false;
  }
  readProperties(kapp->config()); // We need the properties now.
  if ( !createDocking() )
  {
    kdDebug() << "ChatWindow - Couldn't create the docking widgets." << endl;
    return false;
  }
  if ( !initializeContactSidebar() )
  {
    kdDebug() << "ChatWindow - Couldn't setup the contact sidebar." << endl;
    return false;
  }
  if ( !initializeChatView() )
  {
    kdDebug() << "ChatWindow - Couldn't setup the chat view widget." << endl;
    return false;
  }
  if ( !initializeDocking() )
  {
    kdDebug() << "ChatWindow - Couldn't setup the docking widgets." << endl;
    return false;
  }
  if ( !initializeSwitchboardConnection() )
  {
    kdDebug() << "ChatWindow - Couldn't setup the switchboard connection." << endl;
    return false;
  }
  if ( !initializeCurrentAccount() )
  {
    kdDebug() << "ChatWindow - Couldn't setup the current account." << endl;
    return false;
  }
  chatView_->setFocus();
  // Recheck the "showStatusbar" now that the chat view is created
  showStatusBar();
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Check if sidebar should be shown." << endl;
#endif

  // Save the date/time the chat started.
  // Use it to save a chatlog later.
  startDate_ = QDate::currentDate();
  startTime_ = QTime::currentTime();

  initialized_ = true;
  return true;
}



// Set up the main chat view
bool ChatWindow::initializeChatView()
{
#ifdef KMESSTEST
  ASSERT( chatView_ == 0 );
  ASSERT( contactDockWidget_ != 0 );
  ASSERT( mainDockWidget_ != 0);
#endif
  if ( mainDockWidget_ == 0 )
  {
    kdDebug() << "ChatWindow - The main dock widget needs to be created before the chat view." << endl;
    return false;
  }
  // Create the chat view
  chatView_ = new ChatView( mainDockWidget_, "ChatView" );
  if ( chatView_ == 0 )
  {
    kdDebug() << "ChatWindow - Couldn't create a new chat view." << endl;
    return false;
  }
  // Initialize the chat view
  if ( !chatView_->initialize() )
  {
    kdDebug() << "ChatWindow - Couldn't initialize the chat view." << endl;
    return false;
  }
#ifdef KMESSTEST
  ASSERT( chatView_ != 0 );
  ASSERT( contactDockWidget_ != 0 );
  ASSERT( mainDockWidget_ != 0);
#endif
  return true;
}



// Set up the contact sidebar
bool ChatWindow::initializeContactSidebar()
{
#ifdef KMESSTEST
  ASSERT( contactDockWidget_ != 0 );
  ASSERT( contactSidebar_ == 0 );
  ASSERT( mainDockWidget_ != 0);
#endif
  if ( contactDockWidget_ == 0 )
  {
    kdDebug() << "ChatWindow - The contact dock widget needs to be created before the contact sidebar." << endl;
    return false;
  }
  // Create the sidebar
  contactSidebar_ = new ContactSidebar( contactDockWidget_, "ContactSidebar" );
  if ( contactSidebar_ == 0 )
  {
    kdDebug() << "ChatWindow - Couldn't create a new contact sidebar." << endl;
    return false;
  }
  // Initialize the sidebar
  if ( !contactSidebar_->initialize() )
  {
    kdDebug() << "ChatWindow - Couldn't initialize the contact sidebar." << endl;
    return false;
  }

  // connect some signals so that the sidebar can invite
  connect( contactSidebar_,  SIGNAL(   inviteContact( QString )        ),
           this,               SLOT(   inviteContact( QString )        ) );

#ifdef KMESSTEST
  ASSERT( contactDockWidget_ != 0 );
  ASSERT( contactSidebar_ !=0 );
  ASSERT( mainDockWidget_ != 0);
#endif
  return true;
}



// Set up the current account
bool ChatWindow::initializeCurrentAccount()
{
#ifdef KMESSTEST
  ASSERT( contactSidebar_ != 0 );
  ASSERT( currentAccount_ == 0 );
#endif

  if ( contactSidebar_ == 0 )
  {
    kdDebug() << "ChatWindow - The contact sidebar must be initialized before the current account." << endl;
    return false;
  }

  currentAccount_ = CurrentAccount::instance();
  if ( currentAccount_ == 0 )
  {
    kdDebug() << "ChatWindow - Couldn't get an instance of the current account." << endl;
    return false;
  }

  // Set up some widgets based on account settings
  emoticonAction_->setChecked( currentAccount_->getUseEmoticons() );

#ifdef KMESSTEST
  ASSERT( contactSidebar_ != 0 );
  ASSERT( currentAccount_ != 0 );
#endif
  return true;
}



// Set up the docking
bool ChatWindow::initializeDocking()
{
#ifdef KMESSTEST
  ASSERT( chatView_ != 0 );
  ASSERT( contactDockWidget_ != 0 );
  ASSERT( contactSidebar_ !=0 );
  ASSERT( mainDockWidget_ != 0);
#endif
  double splitPercent;

  // Set up the main widget
  mainDockWidget_->setWidget( chatView_ );
  mainDockWidget_->setDockSite( KDockWidget::DockCorner );
  mainDockWidget_->setEnableDocking( KDockWidget::DockNone );
  setView( mainDockWidget_ );
  setMainDockWidget( mainDockWidget_ );

#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Calculating split percent.  Window width is " << width() << "." << endl;
#endif
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Setting size to " << sidebarWidth_ << " x " << sidebarHeight_ << "." << endl;
#endif
  if ( ( sidebarDockPosition_ == KDockWidget::DockLeft ) || ( sidebarDockPosition_ == KDockWidget::DockRight ) )
  { // The sidebar is at the left or right, so calculate a percentage that the contact sidebar should
    //  occupy if the sidebar was on the right
    if ( width() > 0 )
    {
      splitPercent = ( (double)width() - (double)sidebarWidth_ ) / ( (double)width() ) * 100.0;
    }
    else // Avoid div by zero
    {
      splitPercent = 70.0;
    }
    if ( sidebarDockPosition_ == KDockWidget::DockLeft )
    {
      // If the sidebar is really on the left, reverse the split
      splitPercent = 100.0 - splitPercent;
    }
  }
  else if ( ( sidebarDockPosition_ == KDockWidget::DockTop ) || ( sidebarDockPosition_ == KDockWidget::DockBottom ) )
  {
    // The sidebar is at the top or bottom, so create a split percent based on the contact being at the bottom
    if ( height() > 0 )
    {
      splitPercent = ( (double)height() - (double)sidebarHeight_ ) / ( (double)height() ) * 100.0;
    }
    else // Avoid div by zero
    {
      splitPercent = 70.0;
    }
    if ( sidebarDockPosition_ == KDockWidget::DockTop )
    {
      // If the sidebar is really on the top, reverse the split
      splitPercent = 100.0 - splitPercent;
    }
  }
  else
  {
    splitPercent = 50.0;
  }
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Split percent is " << splitPercent << "." << endl;
#endif
  // Set up the sidebar dock, initially on the right hand side
  contactDockWidget_->setWidget( contactSidebar_ );
  contactDockWidget_->manualDock( mainDockWidget_, sidebarDockPosition_, (int)splitPercent );
  return true;
}



// Set up the switchboard connection
bool ChatWindow::initializeSwitchboardConnection()
{
#ifdef KMESSTEST
  ASSERT( msnSwitchboardConnection_ == 0 );
#endif
  if ( contactSidebar_ == 0 )
  {
    kdDebug() << "ChatWindow - The sidebar must be initialized before the switchboard connection." << endl;
    return false;
  }

  // Initialize the switchboard connection
  msnSwitchboardConnection_ = new MsnSwitchboardConnection();
  if ( !msnSwitchboardConnection_->initialize() )
  {
    kdDebug() << "ChatWindow - Couldn't initialize switchboard connection." << endl;
    return false;
  }

  // Make the connections
  connect( msnSwitchboardConnection_, SIGNAL(    contactJoinedChat(QString, QString)                                      ),
           contactSidebar_,           SLOT  (        contactJoined(QString, QString)                                      ) );
  connect( msnSwitchboardConnection_, SIGNAL(      contactLeftChat(QString)                                                ),
           contactSidebar_,           SLOT  (          contactLeft(QString)                                                ) );
  connect( msnSwitchboardConnection_, SIGNAL(        contactTyping(QString, QString)                                                ),
           contactSidebar_,           SLOT  (        contactTyping(QString, QString)                                                ) );

  connect( msnSwitchboardConnection_, SIGNAL(       showAppMessage(QString)                                                ),
           this,                      SLOT  (   receivedAppMessage(QString)                                                ) );
  connect( msnSwitchboardConnection_, SIGNAL(        systemMessage(QString)                                                ),
           this,                      SLOT  (receivedSystemMessage(QString)                                                ) );
  connect( msnSwitchboardConnection_, SIGNAL(    contactJoinedChat(QString, QString)                                      ),
           this,                      SLOT  (        contactJoined(QString, QString)                                      ) );
  connect( msnSwitchboardConnection_, SIGNAL(      contactLeftChat(QString)                                                ),
           this,                      SLOT  (          contactLeft(QString)                                                ) );
  connect( msnSwitchboardConnection_, SIGNAL(        contactTyping(QString, QString)                                                ),
           this,                      SLOT  (        contactTyping(QString, QString)                                                ) );
  connect( msnSwitchboardConnection_, SIGNAL(          chatMessage(QString, QString, QString, QFont, QString, QString) ),
           this,                      SLOT  (      receivedMessage(QString, QString, QString, QFont, QString, QString) ) );

  connect( msnSwitchboardConnection_, SIGNAL(       showAppMessage(QString)                                                ),
           chatView_,                 SLOT  (       showAppMessage(QString)                                                ) );
  connect( chatView_,                 SIGNAL(           appCommand(QString, QString)                                      ),
           msnSwitchboardConnection_, SLOT  (       giveAppCommand(QString, QString)                                      ) );
  connect( msnSwitchboardConnection_, SIGNAL(        systemMessage(QString)                                                ),
           chatView_,                 SLOT  (    showSystemMessage(QString)                                                ) );
  connect( msnSwitchboardConnection_, SIGNAL(          chatMessage(QString, QString, QString, QFont, QString, QString) ),
           chatView_,                 SLOT  (          showMessage(QString, QString, QString, QFont, QString, QString) ) );
  connect( chatView_,                 SIGNAL( sendMessageToContact(QString)                                                ),
           msnSwitchboardConnection_, SLOT  (      sendChatMessage(QString)                                                ) );
  connect( chatView_,                 SIGNAL(         userIsTyping()                                                        ),
           msnSwitchboardConnection_, SLOT  (    sendTypingMessage()                                                        ) );

#ifdef KMESSTEST
  ASSERT( msnSwitchboardConnection_ != 0 );
#endif

  return true;
}



// Invite a contact to the chat
void ChatWindow::inviteContact(QString handle)
{
  if( msnSwitchboardConnection_ != 0 &&
      msnSwitchboardConnection_->isConnected() )
  {
    msnSwitchboardConnection_->inviteContact( handle );
  }
}


// The application is exiting
bool ChatWindow::queryExit()
{
  if(msnSwitchboardConnection_ != 0)
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow::queryExit: immediately closing switchboard connection" << endl;
#endif

    // close the switchboard connection immediately,
    // no proper shutdown because it's not possible anymore.
    // By deleting the object here, the ~ChatWindow destructor
    // can't call closeConnectionLater() for a normal slow/delayed shutdown.
    delete msnSwitchboardConnection_;
    msnSwitchboardConnection_ = 0;
  }

  return true;
}



// The chat window is closing
bool ChatWindow::queryClose()
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow::queryClose: saving chatlog" << endl;
#endif

  // KMessApplication also defines sessionSaving() for KDE 3.1
  KMessApplication *app = static_cast<KMessApplication *>(kapp);

//  if(! app->quitSelected())
//  {
//    // TODO: for KMess 1.5, display that KMess is still visible in the systray.
//    KMessageBox::information( this,
//                              i18n( "Closing the main window will keep KMess running in the "
//                                    "system tray. Use 'Quit' from the 'File' menu to quit the application." ),
//                              i18n( "Docking in System Tray" ), "hideOnCloseInfo" );
//  }


  // Save the chat
  saveChatAutomatically();

  // If the main window is not visible, make sure only this window closes
  if( ! app->mainWidget()->isVisible() )
  {
    hide();
    deleteLater();
    return false;
  }
  else
  {
    // Normal procedure is ok
    return true;
  }
}



// Return whether or not this is an exclusive chat with the given contact
bool ChatWindow::isExclusiveChatWithContact( const QString& handle )
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Checking if chat is exclusive with " << handle << "." << endl;
#endif

  if( msnSwitchboardConnection_ != 0 )
  {
    // Also check if switchboard is disconnected, so we can re-use the chat window.
    return msnSwitchboardConnection_->isExclusiveChatWithContact( handle );
  }

  return false;
}



// Restore the window properties (called by KMainWindow)
void ChatWindow::readProperties(KConfig *config)
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Reading properties." << endl;
#endif

  ChatWindowInterface::readProperties(config);

  config->setGroup("Chat Window");
  sidebarDockPosition_ = (KDockWidget::DockPosition) config->readNumEntry("SidebarDockPosition", KDockWidget::DockRight);
  sidebarWidth_        = config->readNumEntry("SidebarDockWidth",    150 );
  sidebarHeight_       = config->readNumEntry("SidebarDockHeight",   170 );
}



// Notify the user of an application message.
void ChatWindow::receivedAppMessage(QString html)
{
  // If the window doesn't have focus...
  if ( !isActiveWindow()/*hasMouse()*/ )
  {
    // Start the caption blinking
    startBlink();
    // Give the system notification
#if KDE_IS_VERSION(3,2,0)
    KNotifyClient::event(this->winId(), "new message", i18n("Application message"));
#else
    // winid() only required for flashing taskbar anyway, only supported since KDE 3.2
    KNotifyClient::event("new message", i18n("Application message"));
#endif
  }
  checkFirstMessage( "KMess", html);
}



// Notify the user of a system message.
void ChatWindow::receivedSystemMessage(QString message)
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Got a system message." << endl;
#endif
  // If the window doesn't have focus...
  if ( !isActiveWindow()/*hasMouse()*/ )
  {
    // Start the caption blinking
    startBlink();
    // Give the system notification
#if KDE_IS_VERSION(3,2,0)
    KNotifyClient::event(this->winId(), "new message", i18n("System Message"));
#else
    // winid() only required for flashing taskbar anyway, only supported since KDE 3.2
    KNotifyClient::event("new message", i18n("System Message"));
#endif

  }
  checkFirstMessage( "KMess", message );
}



// A message was received from a contact.
void ChatWindow::receivedMessage(QString handle, QString name, QString text, QFont /*font*/, QString /*color*/, QString /*nameColor*/)
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Got a message." << endl;
#endif
  // If the window doesn't have focus...
  if ( !isActiveWindow()/*hasMouse()*/ )
  {
    // Start the caption blinking
    startBlink();

    // Give the system notification
    QString notification = i18n("Message from %1").arg( name );
#if KDE_IS_VERSION(3,2,0)
    KNotifyClient::event(this->winId(), "new message", notification);
#else
    // winid() only required for flashing taskbar anyway, only supported since KDE 3.2
    KNotifyClient::event("new message", notification);
#endif
  }

#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - First message = " << firstMessage_ << "." << endl;
#endif

  // If this is the first message received...
  if ( firstMessage_ )
  {
    firstMessage_ = false;
    // Show the window minimized
    showMinimized();
    chatView_->messageEdit_->setFocus( );
    checkSidebarPreferences();

    // Check if we should autoreply the chat message
    if( currentAccount_           != 0 &&
        msnSwitchboardConnection_ != 0 &&
        currentAccount_->getAutoreply()   )
    {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
      kdDebug() << "ChatWindow - Sending auto reply to chat message." << endl;
#endif
      // Send the autoreply message
      QString awayMessage = currentAccount_->getAutoreplyMessage();
      awayMessage += i18n(" (This message was sent automatically)");
      msnSwitchboardConnection_->sendChatMessage( awayMessage );

      // Show the message in the browser window
      chatView_->showMessage( currentAccount_->getHandle(), currentAccount_->getFriendlyName(),
                              awayMessage, currentAccount_->getFont(), currentAccount_->getFontColor(), "#006A00" );
    }

    // Emit the new message signal so that a balloon will popup
    if ( contactList_ != 0 )
    {
      Contact *contact = contactList_->getContactByHandle( handle );
      if ( contact != 0 )
      {
        emit newChat( contact, text, this );
      }
    }
  }
  // Notify the sidebar that a message was received so that the appropriate contact frame can update its time label.
  if ( contactSidebar_!= 0 )
  {
    contactSidebar_->messageReceived( handle );
  }
  else
  {
    kdDebug() << "ChatWindow - messageReceived() - WARNING: Contact sidebar is null!" << endl;
  }
  // Stop the "user is typing" message
  typingTimer_.stop();
  stopTyping();
}



// Save the chat according to the user's request
void ChatWindow::saveChat()
{
  QString     initialPath, contacts, path;
  QStringList contactList;

  // Show a dialog to get a filename from the user.
  path = KFileDialog::getOpenFileName();
  if( ! path.isNull() )
  {
    saveChatToFile( path );
  }
}



// Save the chat if necessary
void ChatWindow::saveChatAutomatically()
{
#ifdef KMESSTEST
  ASSERT( currentAccount_ != 0 );
#endif
  QTime       currentTime;
  QDate       currentDate;
  QString     basePath, year, month, day, hour, minute, time, file, savePath;
  QDir        dir;
  int         saveStructure;


  // Check if "save chats" is enabled
  if ( ! currentAccount_->getSaveChats() )
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow - User has save chats disabled, not saving chat." << endl;
#endif
    return;
  }


  // Check if the message area is not empty
  if(chatView_->isEmpty() )
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow - Message area is empty, not saving chat." << endl;
#endif
    return;
  }

  // Get the base path
  basePath = currentAccount_->getSaveChatPath();

  dir.setPath( basePath );
  if( ! dir.exists() )
  {
    // TODO: Add a message box here?
    kdWarning() << "ChatWindow - Base directory '" << basePath << "' doesn't exist.  Check your settings." << endl;
    return;
  }

#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Automatically saving the chat." << endl;
#endif

  // Get date strings with numbers padded.
  year    .sprintf("%04d", startDate_.year()   );
  month   .sprintf("%02d", startDate_.month()  );
  day     .sprintf("%02d", startDate_.day()    );
  hour    .sprintf("%02d", startTime_.hour()   );
  minute  .sprintf("%02d", startTime_.minute() );
  time = hour + ":" + minute;

#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Date is " << year << "/" << month << "/" << day << " " << hour << ":" << minute << "." << endl;
#endif

  // Determine the path to save
  saveStructure = currentAccount_->getSavedChatDirectoryStructure();

  switch(saveStructure)
  {
    case Account::BYYEAR :
    {
      // save as chatpath/yyyy/mmdd hh:mm contactemail.html
      if(! dir.exists(year)) dir.mkdir(year);
      dir.cd(year);
      file = "/" + month + "-" + day + " ";
      break;
    }
    case Account::BYMONTH :
    {
      // save as chatpath/yyyy/mm/dd hh:mm contactemail.html
      if(! dir.exists(year))  dir.mkdir(year);
      dir.cd(year);
      if(! dir.exists(month)) dir.mkdir(month);
      dir.cd(month);
      file = "/" + day + " ";
      break;
    }
    case Account::BYDAY :
    {
      // save as chatpath/yyyy/mm/dd/hh:mm contactemail.html
      if(! dir.exists(year))  dir.mkdir(year);
      dir.cd(year);
      if(! dir.exists(month)) dir.mkdir(month);
      dir.cd(month);
      if(! dir.exists(day))   dir.mkdir(day);
      dir.cd(day);
      file = "/";
      break;
    }
    case Account::SINGLEDIRECTORY :
    default :
    {
      // save as chatpath/yyyymmdd hh:mm contactemail.html
      file = "/" + year + "-" + month + "-" + day + " ";
    }
  }

  savePath = dir.absPath() + file + time + " " + firstContact_ + ".html";

  // Save the chat
  saveChatToFile( savePath );
}



// Save the chat to the given file
void ChatWindow::saveChatToFile(QString path)
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - saving chat to '" << path << "'." << endl;
#endif

  // Create and open the file.
  QFile file( path );
  if ( file.open( IO_WriteOnly ) )
  {
    QString text = chatView_->chatBrowser_->text();
    // Make the text show nicely when viewed in an editor.
    text = text.replace( QRegExp("<br>"), "<br>\n" );

    QTextStream t( &file );

    // Tell Qt to output as UTF-8
    t.setEncoding( QTextStream::UnicodeUTF8 );
    // Now tell the HTML viewer that that's what we've done
    t << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">";

    t << text;
    file.close();
  }
  else
  {
    kdDebug() << "ChatWindow: WARNING - File save failed - couldn't open file." << endl;
  }
}



// Save the window properties (called by KMainWindow)
void ChatWindow::saveProperties(KConfig *config)
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Saving properties." << endl;
#endif

  ChatWindowInterface::saveProperties(config);

  config->setGroup("Chat Window");
  config->writeEntry("SidebarDockPosition", sidebarDockPosition_);
  config->writeEntry("SidebarDockWidth",    contactDockWidget_->width() );
  config->writeEntry("SidebarDockHeight",   contactDockWidget_->height() );
}



// "Show status bar" was toggled.
void ChatWindow::showStatusBar()
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Checking the status bar.  showStatusBar_->isChecked() = " << showStatusBar_->isChecked() << "." << endl;
#endif

  // We initialite the chatview later, so ignore the first call from readProperties()
  if ( chatView_ == 0 ) return;


  if( showStatusBar_->isChecked() )
  {
    chatView_->typingLabel_->show();
  }
  else
  {
    chatView_->typingLabel_->hide();
  }
}



// The sidebar is docking
void ChatWindow::sidebarDocking(KDockWidget */*dockWidget*/, KDockWidget::DockPosition dockPosition)
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Tne sidebar is docking at position " << dockPosition << "." << endl;
#endif
  sidebarDockPosition_ = dockPosition;
}



// Start blinking the caption
void ChatWindow::startBlink()
{
  setCaption( caption_.upper() );
  blinkToUpper_ = false;
  blinkTimer_.start( 2000, false );
}



// Start a chat
void ChatWindow::startChat( ChatInformation *chatInfo )
{
#ifdef KMESSTEST
  ASSERT( msnSwitchboardConnection_ != 0 );
#endif
  // Give the sidebar the contact list
  contactSidebar_->setContactList( chatInfo->getContactList() );
  // Give the chat view the contact list
  contactList_ = chatInfo->getContactList();
  // Create the contact actions if they haven't already been created
  if ( contactActions_.count() == 0 )
  {
    createContactActions( chatInfo->getContactList() );
  }

  // Start the chat
  msnSwitchboardConnection_->startChat( chatInfo );
  // If the user started the chat, then the user's message is the first message
  if ( chatInfo->getUserStartedChat() )
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow - Start the chat shown." << endl;
#endif
    if ( isMinimized() ) hide();
    show();
    raise();
    chatView_->messageEdit_->setFocus( );
    checkSidebarPreferences();
    firstMessage_ = false;
  }
  else
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow - Start the chat hidden." << endl;
#endif
    if ( firstMessage_ )
    {
      hide();
    }
  }

  // Save the first contact, to save the chatlogs later
  if(firstContact_.isNull())
  {
    firstContact_ = chatInfo->getContactHandle();
  }
}



// Start GnomeMeeting with a contact.
void ChatWindow::startMeeting()
{
  if ( msnSwitchboardConnection_ != 0 )
  {
    msnSwitchboardConnection_->startApp( MsnSwitchboardConnection::GNOMEMEETING );
  }
}



//Start voice conversation
void ChatWindow::startConversation()
{
  if ( msnSwitchboardConnection_ != 0 )
  {
    msnSwitchboardConnection_->startApp( MsnSwitchboardConnection::VOICECONVERSATION );
  }
}



// Change the status bar message.
void ChatWindow::statusMessage(QString /*message*/)
{
}



// Reset the typing label
void ChatWindow::stopTyping()
{
  if ( chatView_ != 0 )
  {
    chatView_->typingLabel_->setText( "" );
  }
}



// Called when the "use emoticons" action is called.
void ChatWindow::toggleEmoticons(bool useEmoticons)
{
#ifdef KMESSTEST
  ASSERT( currentAccount_ != 0 );
#endif
  // Set the new emoticon use value to the account
  currentAccount_->setUseEmoticons( useEmoticons );
}



// Called when the "show sidebar" action is called.
void ChatWindow::toggleSidebar()
{
#ifdef KMESSTEST
  ASSERT( chatView_ != 0 );
#endif
  contactDockWidget_->changeHideShowState();
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow - Dock site is " << contactDockWidget_->dockSite() << " Main dock site is " << mainDockWidget_->dockSite() << endl;
  kdDebug() << "ChatWindow - Dockwidget height is " << contactDockWidget_->height() << ", width is " << contactDockWidget_->width() << "." << endl;
  kdDebug() << "ChatWindow - contactDockWidget_ visible is " << contactDockWidget_->isVisible() << "." << endl;
#endif
  if ( chatView_ != 0 )
  {
    chatView_->scrollChatToBottom();
  }
  // Toggle the current account's setting.
  if ( currentAccount_ != 0 )
  {
    currentAccount_->setShowSidebar( contactDockWidget_->isVisible() );
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow - Set 'show sidebar' to " << currentAccount_->getShowSidebar() << "." << endl;
#endif
  }
}



// Send a file to a contact.
void ChatWindow::transferFile()
{
#ifdef KMESSDEBUG_CHATWINDOW_FILETRANSFER
  kdDebug() << "ChatWindow:::transferFile()" << endl;
#endif

  if(msnSwitchboardConnection_ != 0)
  {
    // TODO: In the future, we need to show a selection dialog here first.
    QString handle = msnSwitchboardConnection_->getContactList().first();

    // No longer use startApp(FILETRANSFER), let the switchboard
    // deside how to transfer the file:
  //  msnSwitchboardConnection_->startFileTransfer(handle);
    msnSwitchboardConnection_->startApp(MsnSwitchboardConnection::FILETRANSFER);
  }
}



#include "chatwindow.moc"
