Logo Search packages:      
Sourcecode: kmess version File versions  Download package

chatwindow.cpp

/***************************************************************************
                          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 <qapplication.h>
#include <qdict.h>
#include <qdir.h>
#include <qfile.h>
#include <qlayout.h>
#include <qpushbutton.h>
#include <qregexp.h>
#include <qstylesheet.h>
#include <qtextcodec.h>
#include <qtextedit.h>
#include <qtoolbox.h>

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

#include "../actions/accountaction.h"
#include "../contact/contactbase.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 "../network/extra/xmlfunctions.h"
#include "chatview.h"
#include "chatmessageview.h"
#include "contactaction.h"
#include "contactsidebar.h"
#include "emoticonsidebar.h"
#include "websidebar.h"

#ifdef KMESSDEBUG_CHATWINDOW
  #define KMESSDEBUG_CHATWINDOW_GENERAL
  #define KMESSDEBUG_CHATWINDOW_CONTACTS
  #define KMESSDEBUG_CHATWINDOW_FILETRANSFER
#endif

/**
 *  Define the maximum interval (in seconds) between each update of
 *  the status bar label, when displaying "contact is typing" messages.
 */
#define MAXIMUM_TYPING_TIME         5

// The constructor
ChatWindow::ChatWindow(QWidget *parent, const char *name )
 : ChatWindowInterface(parent, name),
   chatView_(0),
   contactSidebar_(0),
   currentAccount_(0),
   customEmoticonSidebar_(0),
   firstMessage_(true),
   initialized_(false),
   lastContact_(QString::null),
   msnSwitchboardConnection_(0),
   standardEmoticonSidebar_(0),
   temporarySidebar_(false),
   webSidebar_(0)
{
  setWFlags(getWFlags() | WDestructiveClose);  // Closing the window destroyes the object too

  statusLabel_ = new KSqueezedTextLabel( this );
  statusBar()->addWidget( statusLabel_, 1 );

  caption_ = i18n("Chat");
  setCaption( caption_ );

  // Connect the blink timer
  connect( &blinkTimer_, SIGNAL(      timeout() ),
           this,         SLOT  ( blinkCaption() ) );
  // Connect the status clear timer
  connect( &statusTimer_,  SIGNAL(             timeout() ),
           this,           SLOT  ( updateTypingMessage() ));
}



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

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

  // Delete the child objects.
  // Also reset pointer, because calls from closeConnectionLater
  // could invoke showMessage().
  delete chatView_;
  chatView_ = 0;

  // Delete the contact actions
  // Avoid crashing when the switchboard/contact fires a "contactLeftChat" signal.
  while( ! contactActions_.isEmpty() )
  {
    ContactAction *action = contactActions_.first();
    if( contactActions_.removeFirst() )
    {
      delete action;
    }
  }

#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( QString( "* " + caption_.upper() ) );
    }
    else
    {
      setCaption( QString( "* " + caption_.lower() ) );
    }
    blinkToUpper_ = !blinkToUpper_;
  }
}



// Toggle the sidebar based on account preferences
void ChatWindow::checkSidebarPreferences()
{
  if(KMESS_NULL(chatView_))                     return;
  if(KMESS_NULL(chatView_->sidebarContainer_))  return;
  if(KMESS_NULL(currentAccount_))               return;

  if( currentAccount_->getShowSidebar() )
  {
    temporarySidebar_ = false;
    chatView_->sidebarContainer_->show();
  }
  else
  {
    temporarySidebar_ = true;
    chatView_->sidebarContainer_->hide();
  }
}



// Choose the contact to start an invitation with.
const QString & ChatWindow::chooseContact()
{
  if( KMESS_NULL(msnSwitchboardConnection_) ) return QString::null;

  // Choose the contact.
  const QStringList &contactsInChat = msnSwitchboardConnection_->getContactsInChat();
  switch( contactsInChat.count() > 1 )
  {
    case 0:
      // No contacts in the chat
      if( msnSwitchboardConnection_->getLastContact().isEmpty() )
      {
        // Send invitation to the first contact that should appear in the chat.
        return msnSwitchboardConnection_->getFirstContact();
      }
      else
      {
        // Resume chat with last contact
        return msnSwitchboardConnection_->getLastContact();
      }

    case 1:
      // Choose the only contact available in the chat
      return contactsInChat.first();

    default:
      // Multiple contacts in the chat.
      // TODO: The official client opens a contact-choose dialog here, and starts a new chat with the selected contact.
      KMessageBox::sorry( this, i18n("You can't start this invitation because there are multiple contacts in this chat.") );
      return QString::null;
  }
}



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

  // Avoid newlines which cause problems to the UI layout.
  caption_ = getCaption().replace( "\n", " " );
  setCaption( caption_ );

  if( ! participants_.contains( handle ) )
  {
    participants_.prepend( handle );
  }

  lastContact_ = QString::null;

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

  // Show message when the user has the sidebar disabled,
  // or when a multi-chat has started.
  if( ( ! chatView_->sidebarContainer_->isVisible() && ! currentAccount_->getShowSidebar() )
  ||  ( msnSwitchboardConnection_ && msnSwitchboardConnection_->getContactsInChat().size() > 1 ) )
  {
    chatView_->showMessage( ChatMessage( ChatMessage::TYPE_NOTIFICATION,
                                         ChatMessage::CONTENT_NOTIFICATION_PRESENCE,
                                         false,
                                         i18n("%1 has joined the chat.").arg(friendlyName),
                                         handle,
                                         friendlyName ) );
  }

  ContactBase *contact = currentAccount_->getContactByHandle( handle );
  if( contact )
  {
    connect( contact, SIGNAL( changedFriendlyName() ), this, SLOT( update() ) );
  }
}



// A contact left the chat
void ChatWindow::contactLeft(QString handle, bool isChatIdle )
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow: contact '" << handle << "' has left" << (isChatIdle ? " due to inactivity." : "") << "." << endl;
#endif

  ContactAction *contactAction = getContactActionByHandle( handle );

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

  // Change the caption to remove the contact who has left
  caption_ = getCaption().replace( "\n", "" );
  setCaption( caption_ );

  participants_.remove( handle );

  // This was the last participant, save it as the last being in so we can
  // still recognize this window as a chat with this contact
  if( participants_.isEmpty() )
  {
    lastContact_ = handle;
  }

  // Show message when the user has the sidebar disabled,
  // or a user left a multi-chat
  if( ( ! chatView_->sidebarContainer_->isVisible() && ! currentAccount_->getShowSidebar() )
  ||  ( msnSwitchboardConnection_ && msnSwitchboardConnection_->getContactsInChat().size() > 0 ) )
  {
    QString contactName = currentAccount_->getContactFriendlyNameByHandle( handle );
    QString message;

    if( isChatIdle )
    {
      message = i18n( "The conversation went idle, %1 has left the chat." ).arg( contactName );
    }
    else
    {
      message = i18n( "%1 has left the chat." ).arg( contactName );
    }

    chatView_->showMessage( ChatMessage( ChatMessage::TYPE_NOTIFICATION,
                                         ChatMessage::CONTENT_NOTIFICATION_PRESENCE,
                                         false,
                                         message,
                                         handle,
                                         contactName ) );
  }

  ContactBase *contact = currentAccount_->getContactByHandle( handle );
  if( contact )
  {
    disconnect( contact, SIGNAL( changedFriendlyName() ), this, SLOT( update() ) );
  }
}



// A contact is typing
void ChatWindow::contactTyping(QString, QString friendlyName)
{
  // If a contact is still typing, re-insert it in the list, so that the typing start time is updated
  if( typingContacts_.contains( friendlyName ) )
  {
    typingContacts_.remove( friendlyName );
  }

  // Add this contact to the list of contacts which are typing, and record when it has started writing
  QTime startTime = QTime::currentTime();
  startTime.start();
  typingContacts_.insert( friendlyName, startTime );

  // Update the actual status bar
  updateTypingMessage();
}



// Create the contact actions
void ChatWindow::createContactActions()
{
  ContactAction          *contactAction;
  QDictIterator<Contact>  it( currentAccount_->getContactList()->getContactList() );
  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;
  }
}



// Disable the parts of the window which allow user interaction, to allow viewing conversations from disconnected accounts
void ChatWindow::disable()
{
  if( msnSwitchboardConnection_ != 0 )
  {
    setSwitchboardConnection( 0 );
  }

  menuBar()->setEnabled( false );
  toolBar("mainToolBar")->setEnabled( false );
  chatView_->messageEdit_->setEnabled( false );
  chatView_->sendButton_->setEnabled( false );
  chatView_->newLineButton_->setEnabled( false );
  chatView_->sidebarContainer_->setEnabled( false );
}




// put the marked text/object into the clipboard and remove
//  it from the document
void ChatWindow::editCut()
{
  if( chatView_->messageEdit_->hasFocus() )
  {
    chatView_->messageEdit_->cut();
  }
}



// put the marked text/object into the clipboard
void ChatWindow::editCopy()
{
  chatView_->editCopy();
}



// Bring up a dialog to change the message font color.
void ChatWindow::editFont()
{
  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 );

    // Commit changes to disk, now
    KConfig *config = kapp->config();
    currentAccount_->saveProperties( config );
  }
}



// Bring up a dialog to change the message font color.
void ChatWindow::editFontColor()
{
  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);

    // Commit changes to disk, now
    KConfig *config = kapp->config();
    currentAccount_->saveProperties( config );
  }
}



// paste the clipboard into the document
void ChatWindow::editPaste()
{
  // Regardless of focus, always paste in message edit.
  chatView_->messageEdit_->paste();
}



// enlarge the font size
void ChatWindow::editZoomIn()
{
  chatView_->changeZoomFactor( true );
}



// decrease the font size
void ChatWindow::editZoomOut()
{
  chatView_->changeZoomFactor( false );
}



// The emoticon button was pressed.
void ChatWindow::emoticonButtonPressed()
{
  // When the side bar is hidden, temporary show it.
  if( ! chatView_->sidebarContainer_->isVisible() )
  {
    temporarySidebar_ = true;
    chatView_->sidebar_->setCurrentItem( standardEmoticonSidebar_ );
    chatView_->sidebarContainer_->show();
  }
  else
  {
    // Sidebar is visible

    // Show the emoticon sidebar
    if( chatView_->sidebar_->currentItem() != standardEmoticonSidebar_ )
    {
      // Show emoticons
      chatView_->sidebar_->setCurrentItem( standardEmoticonSidebar_ );
    }
    else
    {
      // Emotion sidebar is displayed currently
      // Hide the emoticon sidebar
      if( temporarySidebar_ )
      {
        // Hide again
        chatView_->sidebarContainer_->hide();
      }
      else
      {
        // Show Main tab (contacts) again
        chatView_->sidebar_->setCurrentItem( contactSidebar_ );
      }
    }
  }
}



// Forward the application command to the ChatMaster
void ChatWindow::forwardAppCommand(QString cookie, QString contact, QString command)
{
  emit appCommand(cookie, contact, command);
}



// Return a list of the contacts in the chat to be used as window caption
QString ChatWindow::getCaption()
{
  QStringList activeContacts;

  // The chat has no switchboard connection.. it is not even a chat!
  if( msnSwitchboardConnection_ == 0 )
  {
    // If the last contact is empty, it's probably a test chat window.
    if( ! lastContact_.isEmpty() )
    {
      activeContacts << lastContact_;
    }
  }
  else
  {
    // Query the switchboard for the list of participants
    activeContacts = msnSwitchboardConnection_->getContactsInChat();
  }

  // How many people are there in here?
  switch( activeContacts.count() )
  {
    // The chat is empty
    case 0:
      return i18n("Chat");
      break;

    // One contact is connected; or the only contact of the session has left
    case 1:
      return i18n( "%1 - Chat" )
                   .arg( currentAccount_->getContactFriendlyNameByHandle( activeContacts[0] ) );
      break;

    // Two contacts are connected
    case 2:
      return i18n( "%1 and %2 - Chat" )
                   .arg( currentAccount_->getContactFriendlyNameByHandle( activeContacts[0] ) )
                   .arg( currentAccount_->getContactFriendlyNameByHandle( activeContacts[1] ) );
      break;

    // Three or more contacts are connected
    default:
      return i18n( "%1 et al. - Chat" )
                   .arg( currentAccount_->getContactFriendlyNameByHandle( activeContacts[0] ) );
      break;
  }

  // We can't arrive here. Is there a clean way to avoid the useless compiler warning?
  return QString::null;
}



// 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;
}



// Return the switchboard connection used by the chat window.
MsnSwitchboardConnection * ChatWindow::getSwitchboardConnection() const
{
  return msnSwitchboardConnection_;
}



// Initialize the object
bool ChatWindow::initialize( QString handle )
{
  if ( initialized_ )
  {
    kdDebug() << "ChatWindow already initialized." << endl;
    return false;
  }
  readProperties(kapp->config()); // We need the properties now.
  if ( !initializeChatView() )
  {
    kdDebug() << "ChatWindow - Couldn't setup the chat view widget." << endl;
    return false;
  }
  if ( !initializeContactSidebar() )
  {
    kdDebug() << "ChatWindow - Couldn't setup the contacts sidebar widget." << endl;
    return false;
  }
  if ( !initializeEmoticonSidebar() )
  {
    kdDebug() << "ChatWindow - Couldn't setup the emoticon sidebar widget." << endl;
    return false;
  }
  if ( !initializeWebSidebar() )
  {
    kdDebug() << "ChatWindow - Couldn't setup the web sidebar widget." << endl;
    return false;
  }
  if ( !initializeCurrentAccount() )
  {
    kdDebug() << "ChatWindow - Couldn't setup the current account." << endl;
    return false;
  }

  // Pre-register this contact as a participant in the chat window.
  // At this time we still don't have a switchboard, this is needed to ensure that this window is
  // recognized as this contact's window right from the start.
  participants_.clear();
  lastContact_ = handle;

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

  // Update the chat window caption with the contact name
  update();

  initialized_ = true;
  return true;
}



// Set up the main chat view
bool ChatWindow::initializeChatView()
{

  // Create the chat view
  chatView_ = new ChatView( this, "ChatView" );

  if ( chatView_ == 0 )
  {
    kdDebug() << "ChatWindow - Couldn't create a new chat view." << endl;
    return false;
  }

  // Make it read its properties
  chatView_->readProperties( kapp->config() );

  // Initialize it
  if ( ! chatView_->initialize() )
  {
    kdDebug() << "ChatWindow - Couldn't initialize the chat view." << endl;
    return false;
  }

  setCentralWidget( chatView_ );

  connect( chatView_, SIGNAL(             sendFiles(QStringList)               ),
           this,        SLOT( slotStartFileTransfer(QStringList)               ) );
  connect( chatView_, SIGNAL(            appCommand(QString, QString, QString) ),
           this,        SLOT(     forwardAppCommand(QString, QString, QString) ) );
  connect( chatView_, SIGNAL(  sendMessageToContact(QString)                   ),
           this,        SLOT(       userSentMessage()                          ) );

  return true;
}



// Set up the contact sidebar
bool ChatWindow::initializeContactSidebar()
{
#ifdef KMESSTEST
  ASSERT( contactSidebar_ == 0 );
#endif
  if ( chatView_ == 0 )
  {
    kdDebug() << "ChatWindow - The chatview widget needs to be created before the contact sidebar." << endl;
    return false;
  }
  // Create the sidebar
  contactSidebar_ = new ContactSidebar( chatView_->sidebar_, "ContactSidebar" );

  // Initialize the sidebar
  if ( !contactSidebar_->initialize() )
  {
    kdDebug() << "ChatWindow - Couldn't initialize the contact sidebar." << endl;
    return false;
  }

  chatView_->sidebar_->addItem( contactSidebar_, i18n( "Contacts" ) );
  chatView_->sidebar_->setCurrentItem( contactSidebar_ );

  // connect some signals so that the sidebars can send commands back
  connect( contactSidebar_,  SIGNAL(       inviteContact( QString )        ),
           this,               SLOT(       inviteContact( QString )        ) );
  connect( contactSidebar_,  SIGNAL(   setContactAllowed( QString )        ),
           this,             SIGNAL(   setContactAllowed( QString )        ) );
  connect( contactSidebar_,  SIGNAL(     setContactAdded( QString, bool )  ),
           this,             SIGNAL(     setContactAdded( QString, bool )  ) );
  connect( contactSidebar_,  SIGNAL(   setContactBlocked( QString, bool )  ),
           this,             SIGNAL(   setContactBlocked( QString, bool )  ) );

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



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

  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( currentAccount_ != 0 );
#endif
  return true;
}



// Initialize the emoticon chooser
bool ChatWindow::initializeEmoticonSidebar()
{
  if( KMESS_NULL(chatView_) ) return false;

  // Create the emoticon sidebars
  standardEmoticonSidebar_ = new EmoticonSidebar( chatView_->sidebar_, "StandardEmoticonSidebar", false );
  customEmoticonSidebar_ = new EmoticonSidebar( chatView_->sidebar_, "CustomEmoticonSidebar", true );

  // Connect up the emoticon chooser
  connect( standardEmoticonSidebar_, SIGNAL( insertEmoticon( QString ) ),
           chatView_,        SLOT  ( insertEmoticon( QString ) ) );
  connect( customEmoticonSidebar_, SIGNAL( insertEmoticon( QString ) ),
           chatView_,        SLOT  ( insertEmoticon( QString ) ) );

  chatView_->sidebar_->addItem( standardEmoticonSidebar_, i18n( "Emoticons" ) );
  chatView_->sidebar_->addItem( customEmoticonSidebar_, i18n( "My emoticons" ) );

  return true;
}



// Set up the web sidebar
bool ChatWindow::initializeWebSidebar()
{
#ifdef KMESSTEST
  ASSERT( webSidebar_ == 0 );
#endif
  if ( chatView_ == 0 )
  {
    kdDebug() << "ChatWindow - The chatview widget needs to be created before the web sidebar." << endl;
    return false;
  }

  /*
      // This sidebar needs to be improved more


  // Create the sidebar
  webSidebar_ = new WebSidebar( chatView_->sidebar_, "WebSidebar" );

  // Initialize the sidebar
  if ( ! webSidebar_->initialize() )
  {
    kdDebug() << "ChatWindow - Couldn't initialize the web sidebar." << endl;
    return false;
  }

  chatView_->sidebar_->addItem( webSidebar_, i18n("Activities") );


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

  */
  return true;
}



// Invite a contact to the chat
void ChatWindow::inviteContact( QString handle )
{
  if( msnSwitchboardConnection_ == 0 )
  {
    return;
  }

  msnSwitchboardConnection_->inviteContact( handle );
}



// The application is exiting
bool ChatWindow::queryExit()
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow::queryClose: saving chatlog" << endl;
#endif

  // Save the chat before going down!
  saveChatAutomatically();

  return true;
}



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

  // Save the chat
  saveChatAutomatically();

  /**
   * TODO: Uncomment this part: it needs a _reliable_ method to check whether there are running file
   * transfers. The commented part uses "ApplicationList::isEmpty()" which I've removed, the new
   * method to check will probably be elsewhere. The main code of that method was this:
   * <code>
      // check if there are active applications
      if ( mimeApplications_.count() +
          p2pApplications_.count() -
          abortingApplications_.count() == 0)
        return true; else return false;
   * </code>
   */
  /*
  // If there are active applications show a warning to the user
  if( participants_.count() == 1 ) // Transfers are not possibile in multiple chats
  {
    // Check if there are active applications
    ContactBase *contact = currentAccount_->getContactByHandle( participants_.first() );
    if( contact != 0 && contact->getApplicationList() != 0
        && ! contact->getApplicationList()->isEmpty() )
    {
      int choice = KMessageBox::warningContinueCancel( this, i18n("Continue closing the chat window?\nActive transfers will be aborted!"));
      if( choice == KMessageBox::Cancel )
      {
        return false;
      }
    }
  }
  */

  // Notify ChatMaster
  emit closing(this);

  // Delete the contact actions
  // Avoid crashing when the switchboard/contact fires a "contactLeftChat" signal.
  while( ! contactActions_.isEmpty() )
  {
    ContactAction *action = contactActions_.first();
    if( contactActions_.removeFirst() )
    {
      delete action;
    }
  }
  // Delete the contact frames
  // Avoid crashing when the switchboard/contact fires a "contactTyping" signal.
  // Don't delete this in the destructor, QObject will already do this.
  delete contactSidebar_;
  contactSidebar_ = 0;

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



// Return whether or not the contact is in this chat.
bool ChatWindow::isContactInChat( const QString &handle )
{
#ifdef KMESSDEBUG_CHATWINDOW_CONTACTS
  kdDebug() << "ChatWindow::isContactInChat() - Checking handle " << handle << " - participants: " << participants_.join(",") << " - lastContact: " << lastContact_ << endl;
#endif

  if( participants_.isEmpty() )
  {
#ifdef KMESSDEBUG_CHATWINDOW_CONTACTS
    kdDebug() << "ChatWindow::isContactInChat() - returning " << ( lastContact_ == handle ? "true" : "false" ) << endl;
#endif
    return ( lastContact_ == handle );
  }

#ifdef KMESSDEBUG_CHATWINDOW_CONTACTS
  kdDebug() << "ChatWindow::isContactInChat() - returning " << ( participants_.contains( handle ) ? "true" : "false" ) << endl;
#endif
  return participants_.contains( handle );
}



// Return whether or not this is an exclusive chat with the given contact
bool ChatWindow::isExclusiveChatWithContact( const QString& handle )
{
#ifdef KMESSDEBUG_CHATWINDOW_CONTACTS
  kdDebug() << "ChatWindow::isExclusiveChatWithContact() - Checking handle " << handle << " - participants: " << participants_.join(",") << " - lastContact: " << lastContact_ << endl;
#endif
  if( participants_.isEmpty() )
  {
#ifdef KMESSDEBUG_CHATWINDOW_CONTACTS
    kdDebug() << "ChatWindow::isExclusiveChatWithContact() - returning " << ( lastContact_ == handle ? "true" : "false" ) << endl;
#endif
    return ( lastContact_ == handle );
  }

#ifdef KMESSDEBUG_CHATWINDOW_CONTACTS
  kdDebug() << "ChatWindow::isExclusiveChatWithContact() - returning " << ( participants_.contains(handle) && participants_.count() < 2  ? "true" : "false" ) << endl;
#endif
  return ( participants_.contains(handle) && participants_.count() < 2 );
}



// Return whether or not the chat is at its first incoming message
bool ChatWindow::isChatFirstMessage()
{
  return firstMessage_;
}



// Change the switchboard connection used by the chat window.
void ChatWindow::setSwitchboardConnection( MsnSwitchboardConnection *newConnection )
{
  if( KMESS_NULL(chatView_      ) ) return;
  if( KMESS_NULL(contactSidebar_) ) return;

  QStringList participants;
  QStringList::Iterator it;

  // Everything is connected already.
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  if( msnSwitchboardConnection_ == 0 )
  {
    kdDebug() << "ChatWindow::setSwitchboardConnection() - Switchboard was not set up." << endl;
  }
  else
  {
    kdDebug() << "ChatWindow::setSwitchboardConnection() - Switchboard was "
              << ( msnSwitchboardConnection_ == newConnection ? "already set" : "different" ) << "." << endl;
  }
#endif

  if( msnSwitchboardConnection_ != 0 )
  {
    // Notify that all contacts have left the chat
    participants = msnSwitchboardConnection_->getContactsInChat();
    for( it = participants.begin(); it != participants.end(); ++it )
    {
      contactSidebar_->contactLeft( *it, false );
    }

    // Delete all signals between us and the old switchboard
    disconnect( chatView_, 0, msnSwitchboardConnection_, 0 );
    disconnect( msnSwitchboardConnection_, 0, contactSidebar_, 0 );
    disconnect( msnSwitchboardConnection_, 0, this, 0 );

    // Also delete the old connection, if we're setting up a new one
    if( msnSwitchboardConnection_ != newConnection )
    {
      msnSwitchboardConnection_->deleteLater();
    }
  }

  msnSwitchboardConnection_ = newConnection;

  if( newConnection == 0 )
  {
       connect( chatView_, SIGNAL( sendMessageToContact(QString) ),
                this,      SLOT  (    slotSendingFailed(QString) ) );
    return;
  }
  else
  {
    disconnect( chatView_, SIGNAL( sendMessageToContact(QString) ),
                this,      SLOT  (    slotSendingFailed(QString) ) );
  }

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

  connect( msnSwitchboardConnection_, SIGNAL(        contactJoinedChat(QString, QString)             ),
           this,                      SLOT  (            contactJoined(QString, QString)             ) );
  connect( msnSwitchboardConnection_, SIGNAL(          contactLeftChat(QString,bool)                 ),
           this,                      SLOT  (              contactLeft(QString,bool)                 ) );
  connect( msnSwitchboardConnection_, SIGNAL(            contactTyping(QString, QString)             ),
           this,                      SLOT  (            contactTyping(QString, QString)             ) );
  connect( msnSwitchboardConnection_, SIGNAL(              chatMessage(const ChatMessage&)           ),
           this,                      SLOT  (          receivedMessage(const ChatMessage&)           ) );
  connect( msnSwitchboardConnection_, SIGNAL(            receivedNudge(const QString&)               ),
           this,                      SLOT  (        slotReceivedNudge(const QString&)               ) );
  connect( msnSwitchboardConnection_, SIGNAL(            sendingFailed(QString)                      ),
           this,                      SLOT  (        slotSendingFailed(QString)                      ) );
  connect( msnSwitchboardConnection_, SIGNAL(                 deleteMe(MsnSwitchboardConnection*)    ),
           this,                      SLOT  ( setSwitchboardConnection()                             ) );

  connect( chatView_,                 SIGNAL( sendMessageToContact(QString)                  ),
           msnSwitchboardConnection_, SLOT  (      sendChatMessage(QString)                          ) );
  connect( chatView_,                 SIGNAL(         userIsTyping()                                 ),
           msnSwitchboardConnection_, SLOT  (    sendTypingMessage()                                 ) );


  // Add to the sidebar all the contacts that are present in the new switchboard
  participants = msnSwitchboardConnection_->getContactsInChat();
  for( it = participants.begin(); it != participants.end(); ++it )
  {
    contactSidebar_->contactJoined( *it, QString::null );
  }
}



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

  ChatWindowInterface::readProperties( config );

// Decomment should we need to read or save something again
//  config->setGroup("Chat Window");
}



// A message was received from a contact.
void ChatWindow::receivedMessage(const ChatMessage &message)
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow::receivedMessage() - Displaying message, internal type=" << message.getType() << endl;
  kdDebug() << "ChatWindow::receivedMessage() - First message = " << firstMessage_ << "." << endl;
#endif

  if( KMESS_NULL(chatView_) ) return;

  // Find if it's the first message of the chat
  bool isFirstMessage = chatView_->isEmpty();

  chatView_->showMessage( message );

  // If the window doesn't have focus...
  if( ! isActiveWindow() || ! hasFocus() )
  {
    // Start the caption blinking
    startBlink();
  }

  // If this is the first message received...
  if( firstMessage_ )
  {
    // Change the first message property only when we're actually receiving the second message;
    // this way the notification balloons can work correctly. (and maybe future features requiring
    // correct detection of the first message!)
    if( ! isFirstMessage )
      firstMessage_ = false;
  }

  // It's the first message now
  if( isFirstMessage )
  {
    // Update the sidebar
    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 autoreply message
      ChatMessage autoMessage = ChatMessage( ChatMessage::TYPE_OUTGOING,
                                             ChatMessage::CONTENT_MESSAGE,
                                             false,
                                             awayMessage,
                                             currentAccount_->getHandle(),
                                             currentAccount_->getFriendlyName(),
                                             currentAccount_->getImagePath(),
                                             currentAccount_->getFont(),
                                             currentAccount_->getFontColor() );
      chatView_->showMessage( autoMessage );
    }
  }


  // Emit the new message signal so that the user will receive a notification
  if( currentAccount_ != 0 && ! isActiveWindow() )
  {
    emit newChatMessage( message, this );
  }

  // Notify the sidebar, so the contact frame can update itself
  if( ! KMESS_NULL(contactSidebar_) )   // TODO: likely not needed, won't remove it so close before a release.
  {
    contactSidebar_->messageReceived( message.getContactHandle() );
  }

  // Stop the "user is typing" message
  statusLabel_->setText( QString::null );
  statusTimer_.stop();

  // Put back the focus to the message edit box
  chatView_->messageEdit_->setFocus();
}



// Save the chat according to the user's request
void ChatWindow::saveChat()
{
  chatView_->showSaveChatDialog();
}



// Save the chat if necessary
void ChatWindow::saveChatAutomatically()
{
  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 to notify the chat log folder does not exist (and avoid displaying it when KMess is forced to quit).
    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 + " ";
  if( participants_.isEmpty() )
  {
    savePath += lastContact_;
  }
  else
  {
    savePath += participants_.first();
  }


  // Make sure chat logs are not automatically overwritten.
  if( QFile::exists( savePath + ".html" ) )
  {
    kdWarning() << "Chat log " << savePath << ".html already exists!" << endl;
    for( int i = 1; i < 100; i++ )
    {
      if( ! QFile::exists(savePath + "." + QString::number(i) + ".html") )
      {
        savePath += "." + QString::number(i);
        break;
      }
    }
  }

  // Save the chat
  chatView_->saveChatToFile( savePath + ".html" );
}



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

  // Also save ChatView's settings
  chatView_->saveProperties( config );

  ChatWindowInterface::saveProperties( config );

// Decomment should we need to read or save something again
//   config->setGroup( "Chat Window" );
}



// Shake the window, for nudge/buzzer effect.
void ChatWindow::shakeWindow()
{
  QTime t;

  // avoid shaking again in a short period.
  if( lastShake_.secsTo( QTime::currentTime() ) < 15 )
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow::shakeWindow: Not shaking the window after "
              << lastShake_.secsTo(QTime::currentTime()) << " seconds." << endl;
#endif
    return;
  }
  lastShake_ = QTime::currentTime();

  // Shake the window.
  // Code example from: http://ariya.blogspot.com/2005/12/buzz-or-shake-my-window.html
  int xp = x();
  int yp = y();
  t.start();
  for ( int i = 16; i > 0; )
  {
    kapp->processEvents();
    if ( t.elapsed() >= 50 )
    {
      int delta = i >> 2;
      int dir   = i & 3;
      int dx    = (dir == 1 || dir == 2) ? delta : -delta;
      int dy    = (dir < 2) ? delta : -delta;
      move( xp+dx, yp+dy );
      t.restart();
      i--;
    }
  }

  // Move to start again
  move( xp, yp );
}



// "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() )
  {
    statusBar()->show();
  }
  else
  {
    statusBar()->hide();
  }
}



// Show a status message for some seconds in the status dialog
void ChatWindow::showStatusMessage( QString message, int duration )
{
  // Avoid newlines which cause problems to the UI layout.
  message = message.replace( "\n", " " );

  statusLabel_->setText( message );

  if( message.isEmpty() )
  {
    statusTimer_.stop();
  }
  else
  {
    statusTimer_.stop();

    if( duration > 0 )
    {
      statusTimer_.start( duration * 1000, true );
    }
  }
}



// Show a wink received by a contact
void ChatWindow::showWink( const QString &handle, const QString &filename, const QString &/*animationName*/ )
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow: Processing received wink data." << endl;
#endif

  // TODO: move the processing of wink files to a special "WinkArchive" class.

  // Test whether the file exists, before cabextract fails.
  if( ! QFile::exists(filename) )
  {
    kdWarning() << "ChatWindow::showWink: file not found: " << filename << "!" << endl;
    return;
  }

  // Remove extension
  QString dirName = QString(filename).remove( QRegExp("\\.[a-z]+$") );

  QFile metaFile(dirName + "/content.xml");

  // See if the file was extracted already
  if( ! metaFile.exists() )
  {
    // Run cabextract to get the contents of the file
    // -q: quiet, only print errors, -d: output dir, -l: force lowercase (avoiding incompatibilities because win32 is case-insensitive)
    KProcess cabextract(this);
    cabextract.closeStdout();
    cabextract << "cabextract" << "-q" << "-d" << dirName << filename;
    bool started = cabextract.start( KProcess::Block );
    // TODO: capture `cabextract` error messages, display nicely.

    if( ! started )
    {
      chatView_->showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                                           ChatMessage::CONTENT_SYSTEM_ERROR,
                                           false,
                                           i18n("The wink could not be displayed. Make sure you have 'cabextract' installed."),
                                           handle ) );
      return;
    }
    if( ! cabextract.normalExit() || ! metaFile.exists() )
    {
      chatView_->showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                                           ChatMessage::CONTENT_SYSTEM_ERROR,
                                           false,
                                           i18n("The wink could not be displayed. The data could not be read."),
                                           handle ) );
      return;
    }
  }

  // Open the file
  if( ! metaFile.open( IO_ReadOnly ) )
  {
    kdWarning() << "ChatWindow::showWink: file could not be opened: " << metaFile.name() << "!" << endl;
    return;
  }

  // Parse the XML
  QString xmlErrorMessage;
  QDomDocument xml;
  if( ! xml.setContent(&metaFile, true, &xmlErrorMessage) )
  {
    kdWarning() << "ChatWindow::showWink: parsing of wink XML failed: " << xmlErrorMessage << "!" << endl;
    return;
  }

  // Get the package nodes
  QDomElement packageNode = xml.namedItem("package").toElement();
  if( packageNode.isNull() )
  {
    kdWarning() << "ChatWindow::showWink: XML element /package not found!" << endl;
    return;
  }

  if( packageNode.attribute("type") != "wink" )
  {
    kdWarning() << "ChatWindow::showWink: XML element /package/@type is not 'wink'!" << endl;
    return;
  }

  if( packageNode.attribute("contenttype") != "D" )
  {
    kdWarning() << "ChatWindow::showWink: XML element /package/@contenttype is not 'D'!" << endl;
  }

  // Get the child nodes
  QString thumbnail;
  QString animation;
  QString animationType;
  QDomNodeList packageItems = packageNode.childNodes();
  for(uint i = 0; i < packageItems.count(); i++)
  {
    QDomElement child = packageItems.item(i).toElement();
    if( child.tagName() == "item" )
    {
      QString type = child.attribute("type");
      if( type == "animation" )
      {
        animation = child.attribute("file");
        animationType = child.attribute("mimetype");
      }
      else if( type == "thumbnail" )
      {
        thumbnail = child.attribute("file");
        // Type not stored. There are winks with mimetype="image/png" while they're sending a JPG.
      }
      else
      {
        kdWarning() << "ChatWindow::showWink: unknown wink element type: '" << type << "'!" << endl;
      }
    }
  }

  // TODO: clean up weird characters from wink file name.

  if( thumbnail.isEmpty() || animation.isEmpty() || animationType.isEmpty() )
  {
    kdWarning() << "ChatWindow::showWink: XML elements are missing, could not play wink!" << endl;
    return;
  }

  // Escape data for inclusion in HTML.
  animation     = QStyleSheet::escape(animation).replace("'", "&apos;");  // quotes are not escaped by QStyleSheet::escape().
  animationType = animationType.remove( QRegExp("[^a-zA-Z0-9\\-/]") );

  // Height of 150 is a lot, but most winks are designed to be full-screen, so they can hardly be displayed smaller.
  QString html = "<div class='winkContainer'><object width='90%' height='150' align='center' classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'>"
                 "<param name='movie' value='" + dirName + "/" + animation + "' />"
                 "<param name='quality' value='autohigh' />"
                 "<param name='play' value='true' />"
                 "<param name='loop' value='false' />"
                 "<param name='menu' value='false' />"
                 "<param name='align' value='center' />"
                 "<param name='bgcolor' value='#ffffff' />"
                 "<param name='wmode' value='transparent' />"
                 "<embed width='90%' height='150' align='center'"
                 " src='" + dirName + "/" + animation + "' type='" + animationType + "'"
                 " play='true' loop='false' menu='false'"
                 " bgcolor='#ffffff' quality='autohigh' wmode='transparent'></embed></object></div>";

  // Show a notification
  QString friendlyName = currentAccount_->getContactFriendlyNameByHandle(handle);
  receivedMessage( ChatMessage( ChatMessage::TYPE_NOTIFICATION,
                                ChatMessage::CONTENT_NOTIFICATION_WINK,
                                true,
                                i18n("You have received a wink from %1").arg(friendlyName) + html,
                                handle,
                                friendlyName ) );
  // TODO: include contact meta data in ChatMessage() constructor to show wink nicer
}


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



// Start a chat
void ChatWindow::startChat()
{
  if( KMESS_NULL(msnSwitchboardConnection_) )
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdWarning() << "ChatWindow::startChat() - Tried to open a chat without a switchboard!" << endl;
#endif
    return;
  }

  if( ! msnSwitchboardConnection_->isInactive() )
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow::startChat() - Opening a normal chat." << endl;
#endif
    // Initialize an online chat

    // Create the contact actions if they haven't already been created
    if ( contactActions_.count() == 0 )
    {
      createContactActions();
    }
  }
  else
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow::startChat() - Opening an offline chat." << endl;
#endif
    // Initialize an offline chat
    QString handle = msnSwitchboardConnection_->getLastContact();
  }

  // Add the participant contacts to the chat, even if they're offline
  if( ! KMESS_NULL( contactSidebar_ ) )
  {
    QStringList participants = msnSwitchboardConnection_->getContactsInChat();
    QStringList::Iterator it = participants.begin();
    for( ; it != participants.end(); ++it )
    {
      contactSidebar_->contactJoined( *it, QString::null ); // The sidebar slot doesn't use the second parameter
    }
  }

  chatView_->messageEdit_->setFocus();
  checkSidebarPreferences();
}



// Start GnomeMeeting with a contact.
void ChatWindow::startMeeting()
{
  // Choose the contact.
  QString handle = chooseContact();

  if( KMESS_NULL(msnSwitchboardConnection_) )
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdWarning() << "ChatWindow::startMeeting() - Tried to open a meeting with \'" << handle << "\' without a switchboard!" << endl;
#endif
    return;
  }

  emit requestNetMeeting(handle);
}



//Start voice conversation
void ChatWindow::startConversation()
{
  if( KMESS_NULL(msnSwitchboardConnection_) )
  {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdWarning() << "ChatWindow::startConversation() - Tried to open a conversation without a switchboard!" << endl;
#endif
    return;
  }

  kdWarning() << "ChatWindow::startConversation: not implemented!" << endl;
  // msnSwitchboardConnection_->startApp( MsnSwitchboardConnection::VOICECONVERSATION );
}



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



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



// Called when the "show sidebar" action is called.
void ChatWindow::toggleSidebar()
{
  chatView_->scrollChatToBottom();

  if( chatView_->sidebarContainer_->isVisible() )
  {
    chatView_->sidebarContainer_->hide();
  }
  else
  {
    chatView_->sidebarContainer_->show();
  }

  // Toggle the current account's setting.
  if ( currentAccount_ != 0 )
  {
    currentAccount_->setShowSidebar( chatView_->sidebarContainer_->isVisible() );
  
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "ChatWindow - Set 'show sidebar' to " << currentAccount_->getShowSidebar() << "." << endl;
#endif
  }
}



// Signal that a message could not be sent to the switchboard
void ChatWindow::slotSendingFailed( QString message )
{
  message = ( message.length() > 16 ) ? ( message.left( 16 ) + "..." ) : message;

  // We're assuming the chat is empty or broken, so we won't query the switchboard for the contact handle
  chatView_->showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                                       ChatMessage::CONTENT_SYSTEM_ERROR,
                                       false,
                                       i18n("The message '%1' could not be sent.").arg( message ),
                                       lastContact_ ) );

}



// Send a file to a contact.
void ChatWindow::slotStartFileTransfer( QStringList fileList )
{
#ifdef KMESSDEBUG_CHATWINDOW_FILETRANSFER
  kdDebug() << "ChatWindow:::slotStartFileTransfer()" << endl;
#endif
  if( KMESS_NULL(msnSwitchboardConnection_) ) return;

  // Ask for a contact
  QString handle = chooseContact();

  if( fileList.empty() )
  {
#ifdef KMESSDEBUG_CHATWINDOW_FILETRANSFER
    kdDebug() << "ChatWindow:::slotStartFileTransfer() - asking for a file to send" << endl;
#endif

    // If no file names were given, ask for a filename
    QString recentTag = ":fileupload";
    KURL    startDir = KFileDialog::getStartURL(QString::null, recentTag);
    QString filename = KFileDialog::getOpenFileName(startDir.url());

    // Stop if no file was selected
    if( filename.isEmpty() )
    {
      return;
    }

    // Ask the ChatMaster to initiate the file transfer. If there are multiple contacts
    // in this chat, it needs to create a new chat session for that contact.
    emit requestFileTransfer( handle, filename );
  }
  else
  {
    for( QStringList::Iterator it = fileList.begin(); it != fileList.end(); ++it )
    {
#ifdef KMESSDEBUG_CHATWINDOW_FILETRANSFER
      kdDebug() << "ChatWindow:::slotStartFileTransfer() - Sending file " << *it << endl;
#endif
      emit requestFileTransfer( handle, *it );
    }
  }
}



// Notify the user of a received nudge
void ChatWindow::slotReceivedNudge(const QString &handle)
{
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
  kdDebug() << "ChatWindow: Received a nudge." << endl;
#endif

  // Get friendly name
  QString friendlyName = currentAccount_->getContactFriendlyNameByHandle(handle);
  receivedMessage( ChatMessage( ChatMessage::TYPE_NOTIFICATION,
                                ChatMessage::CONTENT_NOTIFICATION_NUDGE,
                                true,
                                i18n("You received a nudge from %1!").arg(friendlyName),
                                handle,
                                friendlyName ) );

  // Start the buzz effect.
  if( currentAccount_->getShakeNudge() )
  {
    shakeWindow();
  }
}



// Send a nudge to a contact
void ChatWindow::slotSendNudge()
{
  if( msnSwitchboardConnection_ == 0 )
  {
    return;
  }

  // Display message
  const QStringList &contactsInChat = msnSwitchboardConnection_->getContactsInChat();
  QString message;
  if( contactsInChat.count() == 1 && msnSwitchboardConnection_->isConnected() )
  {
    message = i18n( "You've sent a nudge to %1!" )
                   .arg( currentAccount_->getContactFriendlyNameByHandle( contactsInChat.first() ) );
  }
  else
  {
    // Chat is either empty (should resume), or a multi-chat
    message = i18n( "You've sent a nudge!" );
  }

  chatView_->showMessage( ChatMessage( ChatMessage::TYPE_NOTIFICATION,
                                       ChatMessage::CONTENT_NOTIFICATION_NUDGE,
                                       false,
                                       message,
                                       QString::null ) );

  // Send nudge
  msnSwitchboardConnection_->sendNudge();

  // Shake the window
  if( currentAccount_->getShakeNudge() )
  {
    shakeWindow();
  }
}



// An event requires the window to update its properties
void ChatWindow::update()
{
  caption_ = getCaption().replace( "\n", " " );
  setCaption( caption_ );
}



// Update the messages which contain custom emoticons
void ChatWindow::updateCustomEmoticon( const QString &handle, const QString &code )
{
  // Relay to chat view.
  chatView_->updateCustomEmoticon(handle, code);
}



// Update the contact typing messages in the status bar
void ChatWindow::updateTypingMessage()
{
  // Allow a maximum number of seconds between each update.
  int nextUpdate = MAXIMUM_TYPING_TIME;

  int secsTo;
  QStringList typingContactsList;
  QMap<QString,QTime>::Iterator it;

  for( it = typingContacts_.begin(); it != typingContacts_.end(); ++it )
  {
    secsTo = (int) ( it.data().elapsed() * .001 ); // Only keep the seconds

    // If the typing event has expired, stop displaying it
    if( secsTo >= MAXIMUM_TYPING_TIME )
    {
      typingContacts_.remove( it.key() );
    }
    else
    {
      // Find the typing event which will expire first, so we can remove that
      if( secsTo && secsTo < nextUpdate )
      {
        nextUpdate = secsTo;
      }

      typingContactsList << it.key();
    }
  }

  // The more time passes from the last update, the more we get close to the next update
  if( nextUpdate < 5 )
  {
    nextUpdate = MAXIMUM_TYPING_TIME - nextUpdate;
  }

  // If no one is typing anymore, clean up the status bar
  if( typingContacts_.count() == 0 )
  {
    statusLabel_->setText( QString::null );
    return;
  }

  QString statusString;

  // Decide what string will be used depending on how many contacts are typing
  if( typingContacts_.count() == 1 )
  {
    statusString = i18n("%1 is typing.").arg( typingContactsList.first() );
  }
  else
  {
    QString first = typingContactsList[0], second = typingContactsList[1];
    int count = typingContactsList.count() - 2;

    if( typingContacts_.count() == 2 )
    {
      statusString = i18n("%1 and %2 are typing.").arg( first ).arg( second );
    }
    else
    {
      statusString = i18n("%1, %2 and %3 others are typing.").arg( first ).arg( second ).arg( count );
    }
  }

  // Set the newly built message to the status bar
  showStatusMessage( statusString, nextUpdate );
}



// The user sent a new message
void ChatWindow::userSentMessage()
{
  // If the emoticon sidebar is still visible, show contact bar again.
  if( chatView_->sidebar_->currentItem() == standardEmoticonSidebar_ )
  {
    chatView_->sidebar_->setCurrentItem( contactSidebar_ );
  }

  // When the sidebar was only temporary visible, hide it again
  if( temporarySidebar_ && ! currentAccount_->getShowSidebar() )
  {
    chatView_->sidebarContainer_->hide();
  }
}



#include "chatwindow.moc"

Generated by  Doxygen 1.6.0   Back to index