Logo Search packages:      
Sourcecode: kmess version File versions

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

#include <kaction.h>
#include <kcolordialog.h>
#include <kdebug.h>
#include <kfiledialog.h>
#include <kfontdialog.h>
#include <klocale.h>
#include <knotifyclient.h>
#include <kmessagebox.h>
#include <kpopupmenu.h>
#include <ksqueezedtextlabel.h>
#include <ktextbrowser.h>
#include <ktoolbarbutton.h>
#include <kstatusbar.h>
#include <kprocess.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 "chatmessage.h"
#include "contactaction.h"
#include "contactsidebar.h"
#include "emoticonsidebar.h"
#include "websidebar.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),
   contactSidebar_(0),
   currentAccount_(0),
   emoticonSidebar_(0),
   firstMessage_(true),
   initialized_(false),
   msnSwitchboardConnection_(0),
   sidebarWidth_(180),
   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  ( slotClearStatus() ));
}



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

  // Test if the switchboard still exists.
  // If the applications is really closing, queryExit() would have destroyed it already.
  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;
  }

#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_;
  }
}



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

  if( currentAccount_->getShowSidebar() )
  {
    temporarySidebar_ = false;
    chatView_->sidebar_->show();
  }
  else
  {
    temporarySidebar_ = true;
    chatView_->sidebar_->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;

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

  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_->sidebar_->isVisible() && ! currentAccount_->getShowSidebar() )
  || msnSwitchboardConnection_->getContactsInChat().size() > 1 )
  {
    chatView_->showMessage( ChatMessage(ChatMessage::TYPE_NOTIFICATION, i18n("%1 has joined the chat.").arg(friendlyName)) );
  }
}



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

  // Show message when the user has the sidebar disabled,
  // or a user left a multi-chat
  if( ( ! chatView_->sidebar_->isVisible() && ! currentAccount_->getShowSidebar() )
  || msnSwitchboardConnection_->getContactsInChat().size() > 0 )
  {
    QString contactName = currentAccount_->getContactFriendlyNameByHandle( handle );
    chatView_->showMessage( ChatMessage(ChatMessage::TYPE_NOTIFICATION, i18n("%1 has left the chat.").arg(contactName)) );
  }

  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)
{
  showStatusMessage( i18n("%1 is typing.").arg( friendlyName ) );
}



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



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



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



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

  // Regardless of focus, always paste in message edit.
  chatView_->messageEdit_->paste();
}



// The emoticon button was pressed.
void ChatWindow::emoticonButtonPressed()
{
#ifdef KMESSTEST
  ASSERT( chatView_ != 0 );
#endif

  // When the side bar is hidden, temporary show it.
  if( ! chatView_->sidebar_->isVisible() )
  {
    temporarySidebar_ = true;
    chatView_->sidebar_->setCurrentItem( emoticonSidebar_ );
    chatView_->sidebar_->show();
  }
  else
  {
    // Sidebar is visible

    // Show the emoticon sidebar
    if( chatView_->sidebar_->currentItem() != emoticonSidebar_ )
    {
      // Show emoticons
      chatView_->sidebar_->setCurrentItem( emoticonSidebar_ );
    }
    else
    {
      // Emotion sidebar is displayed currently
      // Hide the emoticon sidebar
      if( temporarySidebar_ )
      {
        // Hide again
        chatView_->sidebar_->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);
}



// Forward the request for a new chat connection
void ChatWindow::forwardRequestChat(QString handle)
{
  emit requestChat(handle);
}



// 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()
{
  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 ( !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 );
#endif
  // Create the chat view
  chatView_ = new ChatView(this, "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;
  }

  setCentralWidget( chatView_ );

#ifdef KMESSTEST
  ASSERT( chatView_ != 0 );
#endif
  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( 0, "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_->resize( sidebarWidth_, chatView_->sidebar_->height() );

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

#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()
{
#ifdef KMESSTEST
  ASSERT( chatView_ != 0 );
#endif
  if( KMESS_NULL(chatView_) ) return false;

  // Create the emoticon sidebar
  emoticonSidebar_ = new EmoticonSidebar( this, "EmoticonSidebar" );

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

  chatView_->sidebar_->addItem( emoticonSidebar_, i18n("Emoticons") );

  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)             ) );
  connect( msnSwitchboardConnection_, SIGNAL(      contactLeftChat(QString)             ),
           contactSidebar_,           SLOT  (          contactLeft(QString)             ) );
  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)             ),
           this,                      SLOT  (          contactLeft(QString)             ) );
  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(          requestChat(QString)             ),
           this,                      SLOT  (   forwardRequestChat(QString)             ) );

  connect( chatView_,                 SIGNAL(           appCommand(QString, QString, QString)  ),
           this,                      SLOT  (    forwardAppCommand(QString, QString, QString)  ) );
  connect( chatView_,                 SIGNAL( sendMessageToContact(QString)             ),
           msnSwitchboardConnection_, SLOT  (      sendChatMessage(QString)             ) );
  connect( chatView_,                 SIGNAL( sendMessageToContact(QString)             ),
           this,                      SLOT  (      userSentMessage()                    ) );
  connect( chatView_,                 SIGNAL(         userIsTyping()                    ),
           msnSwitchboardConnection_, SLOT  (    sendTypingMessage()                    ) );

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

  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( 0, "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 
  &&  msnSwitchboardConnection_->isConnected() )
  {
    msnSwitchboardConnection_->inviteContact( handle );
  }
  // TODO: also allow to invite a contact when the switchboard is not connected.
}



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

  // Save the chat
  saveChatAutomatically();

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

  // Notify ChatMaster
  emit closing(this);

  // 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 nor the contact is in this chat.
bool ChatWindow::isContactInChat( const QString &handle )
{
  if( msnSwitchboardConnection_ != 0 )
  {
    return msnSwitchboardConnection_->isContactInChat( handle );
  }

  return false;
}



// Return whether or not this is an exclusive chat with the given contact
bool ChatWindow::isExclusiveChatWithContact( const QString& handle )
{
  if( msnSwitchboardConnection_ != 0 )
  {
    // Also check if the contact was the last to leave the chat,
    // which means the chat can be re-used for this contact.
    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");
  sidebarWidth_ = config->readNumEntry("SidebarDockWidth",    150 );
}



// 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;
#endif
  if(KMESS_NULL(chatView_)) return;

  // Show message
  chatView_->showMessage(message);

  // If the window doesn't have focus...
  if ( !isActiveWindow()/*hasMouse()*/ )
  {
    // Start the caption blinking
    startBlink();
    QString notifyMessage;
    QString notifyType = "new message";

    // Generate the correct message.
    int messageType = message.getType();
    switch( messageType )
    {
      case ChatMessage::TYPE_INCOMING:
      case ChatMessage::TYPE_OFFLINE_INCOMING:
      case ChatMessage::TYPE_OUTGOING:  // should not happen, but for consistency
        notifyMessage = i18n("Message from %1").arg( message.getContactName() );
        break;

      case ChatMessage::TYPE_APPLICATION:
        notifyMessage = i18n("Invitation from %1").arg( message.getContactName() );
        break;

      case ChatMessage::TYPE_NOTIFICATION:
        notifyMessage = i18n("Notification message");
        break;

      case ChatMessage::TYPE_SYSTEM:
      default:
        notifyMessage = i18n("An error occured");
        break;
    }

    // Give the system notification
#if KDE_IS_VERSION(3,2,0)
    KNotifyClient::event(this->winId(), notifyType, notifyMessage);
#else
    // winid() only required for flashing taskbar anyway, only supported since KDE 3.2
    KNotifyClient::event(notifyType, notifyMessage);
#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 );

      ChatMessage message = ChatMessage(ChatMessage::TYPE_OUTGOING, awayMessage,
                                        currentAccount_->getHandle(), currentAccount_->getFriendlyName(),
                                        currentAccount_->getCustomImagePath(),
                                        currentAccount_->getFont(), currentAccount_->getFontColor());
      chatView_->showMessage(message);
    }

    // Emit the new message signal so that a balloon will popup
    if( currentAccount_ != 0 )
    {
      if( message.getContactHandle().isEmpty() )
      {
#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
        kdWarning() << "ChatWindow: new chat started with a notification message, no contact handle given to display a balloon." << endl;
#endif
      }
      else
      {
        const ContactBase *contact = currentAccount_->getContactByHandle( message.getContactHandle() );
        if( ! KMESS_NULL(contact) )
        {
          if( message.isNormalMessage() )
          {
            emit newChat( contact, message.getBody(), this );
          }
          else
          {
            // TODO: pass some informative message for notifications so we can display things like "blabla likes to send you a file".
            emit newChat( contact, "...", 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( message.getContactHandle() );
  }
  else
  {
    kdDebug() << "ChatWindow - messageReceived() - WARNING: Contact sidebar is null!" << endl;
  }


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



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



// 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 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 + " " + firstContact_;


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

  ChatWindowInterface::saveProperties(config);

  config->setGroup("Chat Window");
  config->writeEntry("SidebarDockWidth",   chatView_->sidebar_->width() );
}



// 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 temporary in the status dialog
void ChatWindow::showStatusMessage( const QString &message, int duration )
{
  statusLabel_->setText( message );

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

    if( duration > 0 )
    {
      statusTimer_.start(duration, 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,
                              i18n("The wink could not be displayed. Make sure you have 'cabextract' installed.")) );
      return;
    }
    if( ! cabextract.normalExit() || ! metaFile.exists() )
    {
      chatView_->showMessage( ChatMessage(ChatMessage::TYPE_SYSTEM,
                              i18n("The wink could not be displayed. The data could not be read.")) );
      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, i18n("You have received a wink from %1").arg(friendlyName) + html, handle ) );
  // 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( const ChatInformation &chatInfo )
{
#ifdef KMESSTEST
  ASSERT( msnSwitchboardConnection_ != 0 );
#endif

  // Create the contact actions if they haven't already been created
  if ( contactActions_.count() == 0 )
  {
    createContactActions();
  }

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



// Initialize a chat window, pretending the contact is in the chat
void ChatWindow::startOfflineChat( const QString &handle )
{
  // This prevents multiple chat windows from being spawned,
  // register the contact with this chat connection.
  if( ! KMESS_NULL(msnSwitchboardConnection_) )
  {
    msnSwitchboardConnection_->startOfflineChat(handle);
  }

  if(! KMESS_NULL(contactSidebar_) )
  {
    contactSidebar_->contactJoined(handle);
  }

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

  // The startChat() method will be called later when the real chat starts.
}



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

  if( KMESS_NULL(msnSwitchboardConnection_) ) return;

  // Choose the contact.
  handle = chooseContact();

  emit requestNetMeeting(handle);
}



//Start voice conversation
void ChatWindow::startConversation()
{
  if ( msnSwitchboardConnection_ != 0 )
  {
    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)
{
#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

  if ( chatView_ != 0 )
  {
    chatView_->scrollChatToBottom();
  }

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

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



// Reset the status label
void ChatWindow::slotClearStatus()
{
  statusLabel_->setText( QString::null );
}



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

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

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



// 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,
                   i18n("You received a nudge from %1!").arg(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();
  if( contactsInChat.count() == 1 )
  {
    QString friendlyName = currentAccount_->getContactFriendlyNameByHandle( contactsInChat.first() );
    chatView_->showMessage( ChatMessage(ChatMessage::TYPE_NOTIFICATION,
                                        i18n("You've sent a nudge to %1!").arg(friendlyName)) );
  }
  else
  {
    // Chat is either empty (should resume), or a multi-chat
    chatView_->showMessage( ChatMessage(ChatMessage::TYPE_NOTIFICATION, i18n("You've sent a nudge!")) );
  }

  // Send nudge
  msnSwitchboardConnection_->sendNudge();

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



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



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

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



#include "chatwindow.moc"

Generated by  Doxygen 1.6.0   Back to index