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

chatview.cpp

/***************************************************************************
                          chatview.cpp  -  description
                             -------------------
    begin                : Wed Jan 15 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 "chatview.h"

#include <qclipboard.h>
#include <qcolor.h>
#include <qdragobject.h>
#include <qvbox.h>
#include <qlayout.h>
#include <qregexp.h>
#include <qpushbutton.h>
#include <qstringlist.h>
#include <qtextbrowser.h>
#include <qtextcodec.h>
#include <qtextedit.h>
#include <qtoolbox.h>

#include <kaction.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kfiledialog.h>
#include <kglobal.h>
#include <khtmlview.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kpopupmenu.h>
#include <krun.h>
#include <kstddirs.h>
#include <ktextbrowser.h>
#include <kurl.h>

#include "chatmessage.h"
#include "chatmessagestyle.h"
#include "chatmessageview.h"

#include "../contact/contactbase.h"
#include "../dialogs/addemoticondialog.h"
#include "../currentaccount.h"
#include "../emoticonmanager.h"
#include "../kmessdebug.h"

// The constructor
ChatView::ChatView(QWidget *parent, const char *name )
 : ChatViewInterface(parent,name),
   chatStyle_(0),
   currentAccount_(0),
   doSendTypingMessages_(true),
   initialized_(false),
   messageBarHeight_(100),
   sidebarWidth_(140)
{
  // Insert a KHTMLPart in the placeholder
  chatMessageView_ = new ChatMessageView( khtmlPlaceholder_, "ChatMessageView" );
  connect( chatMessageView_, SIGNAL(           popupMenu(const QString&, const QPoint&) ),
           this,             SLOT  ( slotShowContextMenu(const QString&, const QPoint&) ));
  connect( chatMessageView_, SIGNAL(      openURLRequest(const KURL&, const KParts::URLArgs&) ),
           this,               SLOT(  slotOpenURLRequest(const KURL&, const KParts::URLArgs&) ) );

  // Create a layout to maximize the KHTMLPart
  QBoxLayout *messageLayout = new QHBoxLayout( khtmlPlaceholder_ );
  messageLayout->addWidget( chatMessageView_->view() );   // Stretches widget

  // Set default colors of message edit, because the text color will be overwritten with the defined one.
  messageEdit_->setPaletteBackgroundColor( Qt::white );
  messageEdit_->setPaletteForegroundColor( Qt::black );

  // Connect the message edit so that if its displayed color changes,
  // it checked to match the user's chosen color.
  connect( messageEdit_, SIGNAL( currentColorChanged( const QColor & ) ),
           this,         SLOT  (  editorColorChanged( const QColor & ) ) );


  connect( messageEdit_, SIGNAL( currentColorChanged( const QColor & ) ),
           this,         SLOT  (  editorColorChanged( const QColor & ) ) );

  // Set initial widget state
  messageEdit_->setFocus();
  sendButton_   ->setEnabled( false );
  sendButton_   ->setFocusPolicy( QWidget::NoFocus );
  newLineButton_->setFocusPolicy( QWidget::NoFocus );
  messageSplitter_->setResizeMode( messageBar_, QSplitter::KeepSize );
  sidebarSplitter_->setResizeMode( sidebarContainer_, QSplitter::KeepSize );

  sidebar_->removeItem( sidebar_->currentItem() );

  // Enable drag&drop of files to the message box
  messageEdit_->setAcceptDrops( true );
  messageEdit_->installEventFilter( this );
  messageEdit_->viewport()->installEventFilter( this );

  // And to the messages view
  chatMessageView_->view()->setAcceptDrops( true );
  chatMessageView_->view()->installEventFilter( this );

  // Enable auto-delete
  chatMessages_.setAutoDelete(true);
}


// The destructor
ChatView::~ChatView()
{
  // Clear messages, chat style
  chatMessages_.clear();
  delete chatStyle_;
}



// Delete the newline behind the message edit's cursor.
void ChatView::deleteNewlineAtCursor()
{
  int      endpara, endindex, startpara, startindex;

  // Get the cursor's position in the message edit
  messageEdit_->getCursorPosition(&endpara, &endindex);

  // The position before the cursor will be one index behind the end,
  //  unless the index is zero.
  if ( endindex == 0 )
  {
    startpara = endpara - 1;
    startindex = messageEdit_->paragraphLength( startpara );
    // Select the character behind the cursor
    messageEdit_->setSelection(startpara, startindex, endpara, endindex);
    // Delete the selection, which should be a newline.
    messageEdit_->del();
  }
}



// Event filter to detect special actions in the message editor; also detect those of the message view. They get filtered
// here since the message view has a NoFocus policy, which prevents it from having keyboard shortcut events.
bool ChatView::eventFilter( QObject */*obj*/, QEvent *event )
{
  QStringList fileList;
  QKeyEvent  *keyEvent  = static_cast<QKeyEvent*> ( event );
  QDropEvent *dropEvent = static_cast<QDropEvent*>( event );

  switch( event->type() )
  {
    case QEvent::DragEnter:
      dropEvent->accept( QUriDrag::decodeLocalFiles( dropEvent, fileList ) );
      break;

    case QEvent::Drop:
      if( QUriDrag::decodeLocalFiles( dropEvent, fileList ) )
      {
        // Process the drop only if it comes from out of our application.
        if( dropEvent->source() == 0 )
        {
          #ifdef KMESSDEBUG_CHATVIEW
                  kdDebug() << "ChatView::eventFilter() - Drag'n'dropped files: " << fileList.join( " - " ) << endl;
          #endif
          emit sendFiles( fileList );
        }

        // Blocks further handling of this event.
        keyEvent->ignore();
        return true;
      }
      break;

    case QEvent::KeyPress:
      // Detect if ctrl+enter or shift+enter has been pressed, to insert a new line
      // instead of sending the message
      if( (keyEvent->state() &  Qt::ShiftButton || keyEvent->state() &  Qt::ControlButton )
      &&  (keyEvent->key()   == Qt::Key_Return  || keyEvent->key()   == Qt::Key_Enter     ) )
      {
        messageEdit_->insert( "\n" );
        // Blocks further handling of this event.
        keyEvent->ignore();
        return true;
      }
      // Detect text copying from the Message View
      else if( keyEvent->state() & Qt::ControlButton && keyEvent->key() == Qt::Key_C )
      {
        editCopy();
      }
      // Detect 'save chat' command
      else if( keyEvent->state() & Qt::ControlButton && keyEvent->key() == Qt::Key_S )
      {
        showSaveChatDialog();
      }
      break;

    default:
      break;
   }

  return false;
}



// The color in the text box changed.
void ChatView::editorColorChanged(const QColor &color)
{
#ifdef KMESSTEST
  ASSERT( currentAccount_ != 0 );
#endif
  // Sometimes the text box color seems to spontaneously reset to black.
  // If this happened, set the color back to the user's color.
  if ( color.name() != currentAccount_->getFontColor() )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "ChatView - Restore the color to the account setting by calling 'updateEditorFont'." << endl;
#endif
    updateEditorFont();
  }
}



// Copy the currently selected text
void ChatView::editCopy()
{
  if( messageEdit_->hasSelectedText() )
  {
    messageEdit_->copy();
  }
  else if( chatMessageView_->hasSelection() )  // never has focus, only selection.
  {
    kapp->clipboard()->setText( chatMessageView_->selectedText() );
    // setData() for HTML version.
  }
}



// Set the zoom factor of the text
void ChatView::changeZoomFactor( bool increase )
{
  int newZoom = chatMessageView_->zoomFactor();

  if( increase )
  {
    newZoom += 25;
  }
  else
  {
    newZoom -= 25;
  }

  if( newZoom > 20 && newZoom < 300 )
  {
    currentZoom_ = newZoom;
    chatMessageView_->setZoomFactor( currentZoom_ );
  }
}


// The user pressed return in the message editor, so send the message
void ChatView::enterPressed()
{
  if(! messageEdit_->text().isEmpty())
  {
    // Don't send any typing messages while preparing to send the message in the text box...
    doSendTypingMessages_ = false;

    // Pressing enter caused a newline to be entered under the cursor... remove it.
    deleteNewlineAtCursor();

    // Send the message.
    sendMessage();

    // Now messages can again be sent
    doSendTypingMessages_ = true;
  }
}



// Initialize the object
bool ChatView::initialize()
{
#ifdef KMESSDEBUG_CHATVIEW
  kdDebug() << "ChatView: initializing." << endl;
#endif

  if ( initialized_ )
  {
    kdDebug() << "ChatView already initialized." << endl;
    return false;
  }
  if ( !initializeCurrentAccount() )
  {
    return false;
  }

  // Set the default widget sizes
  sidebarContainer_->resize( sidebarWidth_, sidebarContainer_->height() );
  messageBar_->resize( messageBar_->width(), messageBarHeight_ );

  // Restore the level of zoom in the chat messages view
  chatMessageView_->setZoomFactor( currentZoom_ );

  // Get chat style
  chatStyle_ = new ChatMessageStyle();
  updateChatStyle();

  initialized_ = true;
  return true;
}



// Initialize the current account
bool ChatView::initializeCurrentAccount()
{
  currentAccount_ = CurrentAccount::instance();
  if ( currentAccount_ == 0 )
  {
    kdDebug() << "ChatView - Couldn't get the instance of the current account." << endl;
    return false;
  }
  connect( currentAccount_, SIGNAL(      changedFontSettings() ),
           this,            SLOT  (         updateEditorFont() ) );
  connect( currentAccount_, SIGNAL( changedChatStyleSettings() ),
           this,            SLOT  (          updateChatStyle() ) );
  updateEditorFont();
  return true;
}



// Return the encoding of the HTML document.
QString ChatView::getEncoding() const
{
  return chatMessageView_->encoding();
}



// Return the HTML source of the page.
QString ChatView::getHtml() const
{
  return chatMessageView_->getHtml();
}



// Return the selected text in the message area.
QString ChatView::getSelectedText(bool asHtml) const
{
  if( asHtml )
  {
#if KDE_IS_VERSION(3,4,0)
    return chatMessageView_->selectedTextAsHTML();
#else
    return chatMessageView_->selectedText();
#endif
  }
  else
  {
    return chatMessageView_->selectedText();
  }
}



// Insert an emoticon into the message editor
void ChatView::insertEmoticon( QString emoticonText )
{
  // Insert the text at the cursor.
  messageEdit_->insert( emoticonText );
  // Delete the newline there
  deleteNewlineAtCursor();
}



// Whether or not the message area is empty
bool ChatView::isEmpty() const
{
  return chatMessageView_->isEmpty();
}



// The message text changed, so the user is typing
void ChatView::messageTextChanged()
{
  if ( doSendTypingMessages_ )
  {
    // Disable or enable the send button depending on whether or not the message edit is empty
    if ( messageEdit_->text().isEmpty() )
    {
      sendButton_->setEnabled( false );
    }
    else
    {
      sendButton_->setEnabled( true );

      // If the last character of the message is a newline, sending the typing signal will
      //  cause the actual text message to be received twice, so check the last character...
      if ( messageEdit_->text().right(1) != "\n" )
      {
        // If the typing timer isn't already going...
        if ( !userTypingTimer_.isActive() )
        {
          emit userIsTyping();
          userTypingTimer_.start( 4000, true );
        }
      }
    }
  }
}



// The user clicked the new line button so insert a new line in the editor
void ChatView::newLineClicked()
{
  messageEdit_->insert("\n");
}



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

  config->setGroup( "Chat View" );
  sidebarWidth_      = config->readNumEntry( "SidebarWidth"     , 140 );
  messageBarHeight_  = config->readNumEntry( "MessagebarHeight" , 100 );
  currentZoom_       = config->readNumEntry( "CurrentZoom"      , 100 );
}



// Save the chat to the given file
void ChatView::saveChatToFile( const QString &path )
{
#ifdef KMESSDEBUG_CHATVIEW
  kdDebug() << "ChatView: saving chat to '" << path << "'." << endl;
#endif

  // Create and open the file.
  QFile file( path );
  if( ! file.open( IO_WriteOnly ) )
  {
    kdWarning() << "ChatView: File save failed - couldn't open file '" << path << "'." << endl;
    KMessageBox::sorry( this, i18n("Could not save chat log. Make sure you have permission to write in the folder where logs are being saved.") );
    return;
  }

  // Get the text
  // Make the text show nicely when viewed in an editor.
  QString text = getHtml();
  text = text.replace( QRegExp("<br>"), "<br>\n" );

  // Get encoding
  QTextCodec *codec = QTextCodec::codecForName(getEncoding());
  if(codec == 0)
  {
    kdWarning() << "Could not find codec '" << getEncoding() << "', special characters might not be saved correctly!" << endl;
  }
#ifdef KMESSDEBUG_CHATVIEW
  else
  {
    kdDebug() << "ChatView: found codec '" << codec->name() << "' for '" << getEncoding() << "'." << endl;
  }
#endif

  // Output the HTML with the right text encoding
  QTextStream textStream( &file );
  if(codec != 0)
  {
    textStream.setCodec( codec );
  }
  textStream << text;
  file.close();
}



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

  config->setGroup( "Chat View" );
  config->writeEntry( "SidebarWidth"     , sidebarContainer_->width() );
  config->writeEntry( "MessagebarHeight" , messageBar_->height()      );
  config->writeEntry( "CurrentZoom"      , currentZoom_               );
}



// Scroll to the bottom of the chat browser
void ChatView::scrollChatToBottom()
{
  chatMessageView_->scrollChatToBottom();
}



// The user clicked send, so send the message
void ChatView::sendClicked()
{
  sendMessage();

  // Ensure the typing area always gets focus,
  // so the user can type the next message directly.
  // This area looses focus when you do other things like choosing emoticons.
  messageEdit_->setFocus();
}



// Send a message via the server
void ChatView::sendMessage()
{
  uint maxSendableMessageLength = 1400;

  QString text = messageEdit_->text();

  if(! text.isEmpty())
  {
    messageEdit_->clear();

    // If the text is longer than the sendable amount, put the remainder back in the text edit.
    // Since the message will be sent as UTF8, it's the UTF8 length we have to consider.
    QCString utf8Text = text.utf8();
    if ( utf8Text.length() > maxSendableMessageLength )
    {
      // If so, then divide the text into the first part and a remainder.
      QCString remainder = utf8Text.right( utf8Text.length() - maxSendableMessageLength );
      text               = QString::fromUtf8( utf8Text.left( maxSendableMessageLength ) );

      // Return the remainder to the message edit.
      messageEdit_->setText( QString::fromUtf8( remainder ) );
    }

    // Replace "\n"s with "\r\n"s
    text = text.replace( QRegExp("\n"), "\r\n" );

    // Ask the server to send the message to the contact(s)
    emit sendMessageToContact( text );

    // Reset the typing timer, so it restarts correctly
    userTypingTimer_.stop();

    // Show the message in the browser window
    ChatMessage message(ChatMessage::TYPE_OUTGOING,
                        ChatMessage::CONTENT_MESSAGE,
                        false,
                        text,
                        currentAccount_->getHandle(),
                        currentAccount_->getFriendlyName(),
                        currentAccount_->getImagePath(),
                        currentAccount_->getFont(),
                        currentAccount_->getFontColor() );

    showMessage(message);
  }
}



// Add the given message to the message browser.
void ChatView::showMessage(const ChatMessage &message)
{
  if(KMESS_NULL(chatStyle_)) return;

  // Clone the message so it can be stored in the local qptrlist objects.
  ChatMessage *chatMessage = message.clone();
  QString messageHtml;

  // See if the same contact sent the previous message too.
  // In that case, we combine both messages for the chat style.
  // Otherwise the lastContactMessages_ list is reset.
  if( message.isNormalMessage() && currentAccount_->getGroupFollowupMessages() )
  {
    ChatMessage *lastContactMessage = lastContactMessages_.last();
    if( lastContactMessage != 0 )
    {
      // Check for contact handle and message type, so offline messages won't be
      // grouped with normal incoming messages.
      if( lastContactMessage->getContactHandle() == message.getContactHandle()
      &&  lastContactMessage->getType()          == message.getType() )
      {
        lastContactMessages_.append(chatMessage);
      }
      else
      {
        lastContactMessages_.clear();
      }
    }
  }
  else
  {
    lastContactMessages_.clear();
  }

  // Convert the message, add to the browser.
  if( lastContactMessages_.count() > 1 )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "ChatView::showMessage: replacing last contact message with new contents." << endl;
#endif

    messageHtml = chatStyle_->convertMessageList(lastContactMessages_);
    chatMessageView_->replaceLastMessage(messageHtml);
  }
  else
  {
#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "ChatView::showMessage: appending new message." << endl;
#endif

    messageHtml = chatStyle_->convertMessage(message);
    chatMessageView_->addHtmlMessage(messageHtml);

    if( message.isNormalMessage() )
    {
      lastContactMessages_.append( chatMessage );
    }
  }


  // If the parser found custom emoticons, add them.
  // The pending list of chatStyle is erased with each new convertMessage() call.
  const QStringList &emoticonTags = chatStyle_->getPendingEmoticonTagIds();
  if( ! emoticonTags.isEmpty() )
  {
    // Copy to internal list.
    for(QStringList::const_iterator it = emoticonTags.begin(); it != emoticonTags.end(); ++it)
    {
      pendingEmoticonTags_.append(*it);
    }
  }


  // Add to memory, this is used for:
  // - changing chat styles
  // - regenerating messages to group them
  // - regenerating messages for received custom emoticons.
  chatMessages_.append( chatMessage );
}



// Show a dialog to save the chat.
void ChatView::showSaveChatDialog()
{
  QFileInfo targetFile;
  int overwriteAllowed;
  QString path = currentAccount_->getSaveChatPath() + "/kmess-chat.html"; // Default name for the saved chat

  // Repeat until a file is chosen or the operation is cancelled
  do
  {
    // Show a dialog to get a filename from the user.
    path = KFileDialog::getSaveFileName( path, "*.html" );

    // Verify if the user has canceled the command
    if( path.isEmpty() )
    {
      return;
    }

    // Default to accept the path name
    overwriteAllowed = KMessageBox::Ok;

    // Check if the file exists and if so, warn the user
    targetFile.setFile( path );
    if( targetFile.exists() )
    {
      overwriteAllowed = KMessageBox::warningContinueCancel(
                                0,
                                i18n("The file '%1' already exists.\ndo you want to overwrite it?").arg( targetFile.fileName() ),
                                i18n("Overwrite File"),
                                KGuiItem( i18n("Over&write") ) );
    }

  } while( overwriteAllowed == KMessageBox::Cancel );

  saveChatToFile( path );
}



/**
 * Open a dialog to add a new custom emoticon seen in the chat
 */
00709 void ChatView::slotAddNewEmoticon()
{
  if( ! chatViewClickedUrl_.isValid() || chatViewClickedUrl_.protocol() != "kmess" || chatViewClickedUrl_.host() != "emoticon" )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "ChatView::slotAddNewEmoticon() - Ignoring request for invalid URL: " << chatViewClickedUrl_.url() << endl;
#endif

    return;
  }

/*
 * Everytime we generate a link to add an emoticon, we have only access to HTML representations of the image, that means tags like:
 * <img src='filename.png&apos; ... />
 * So the least resource-intensive and easy - read: fast - way of passing the image name of the new emoticon to this method is
 * a raw url-encoding of the entire tag, which we decode and split up to extract the filename from the 'src' tag attribute.
 * Hacky, but hopefully with Qt4 we'll find another way.
 *
 * KMess' internal emoticon addition URLs are in the form
 * <code>kmess://emoticon/contactHandle/urlEncodedShortcut/urlEncodedPictureTag</code>
 */

  QString path  = chatViewClickedUrl_.path().mid( 1 );  // remove first /

#ifdef KMESSTEST
  ASSERT( ! path.isEmpty() );
#endif

  // Reset the url
  chatViewClickedUrl_ = KURL();

  QString handle   = path.section( "/", 0, 0 );   // First parameter: contact handle
  QString shortcut = path.section( "/", 1, 1 );   // Second parameter: emoticon shortcut
  QString picture  = path.section( "/", 2    );   // Third parameter: emoticon picture (as an HTML <IMG> tag)

  const ContactBase *contact = CurrentAccount::instance()->getContactByHandle( handle );
  if( contact == 0 )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "ChatView::slotAddNewEmoticon() - Contact handle not found: " << handle << endl;
#endif

    return;
  }


  // URL-Decode the encoded strings
  picture  = KURL::decode_string( picture  );
  shortcut = KURL::decode_string( shortcut );

  // Strip the emoticon picture name from the tag
  picture = picture.replace( "\"", "'" );
  picture = picture.mid( picture.find( "src='" ) + 5 );
  picture = picture.mid( 0, picture.find( "'" ) );

#ifdef KMESSDEBUG_CHATVIEW
  kdDebug() << "ChatView::slotAddNewEmoticon() - Showing Add Emoticon dialog - shortcut=" << shortcut << " picture=" << picture << endl;
#endif

  // Finally, show the dialog, preselecting the picture file and shortcut
  AddEmoticonDialog *addDialog = new AddEmoticonDialog( EmoticonManager::instance()->getTheme( true ), this, "AddEmoticonDialogChat" );

  connect( addDialog,        SIGNAL( addedEmoticon(QString) ),
           chatMessageView_,   SLOT( addedEmoticon(QString) ) );

  addDialog->preSelect( picture, shortcut );

}



// The user clicked the "copy address" or "copy email" option in the context menu
void ChatView::slotCopyAddress()
{
  QString url = chatViewClickedUrl_.url();

  if( ! chatViewClickedUrl_.isValid() )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "ChatView::slotCopyAddress() - Not copying invalid URL: " << url << endl;
#endif

    return;
  }

#ifdef KMESSDEBUG_CHATVIEW
  kdDebug() << "ChatView::slotCopyAddress() - Copying URL: " << url << endl;
#endif

  // Strip the pseudo-protocol 'mail to' from email addresses
  if( url.left( 7 ) == "mailto:" )
  {
    url = url.mid( 7 );
  }

  kapp->clipboard()->setText( url );

  // Reset the url
  chatViewClickedUrl_ = KURL();
}



// The user clicked the "copy text" option in the context menu.
void ChatView::slotCopyChatText()
{
  kapp->clipboard()->setText( this->getSelectedText( false ) );
}



// Open a new url clicked in the khtml widget
void ChatView::slotOpenURLRequest( const KURL &url, const KParts::URLArgs &/* urlArgs */ )
{
    chatViewClickedUrl_ = url;

    // Internal URLs form: kmess://call_type/parameters?more_parameters

    if( url.protocol() == "kmess" )
    {
      // Application URLs form: kmess://application/responseType/contactHandle?cookieId
      if( url.host() == "application" )
      {
        // Handle the applications input
        slotSendAppCommand();
      }
      // Emoticon URLs form: kmess://emoticon/contactHandle/urlEncodedShortcut/urlEncodedPictureTag
      else if( url.host() == "emoticon" )
      {
        // Add a new emoticon
        slotAddNewEmoticon();
      }
    }
    else
    {
      // Just execute the link
      slotVisitAddress();
    }
}



// The user clicked the "select all" option in the context menu.
void ChatView::slotSelectAllChatText()
{
  chatMessageView_->selectAll();
}



/**
 * The user clicked a kmess internal link in the ChatMessageView
 */
00862 void ChatView::slotSendAppCommand()
{
  // Ignore non-internal links and non-application internal links
  if( ! chatViewClickedUrl_.isValid() || chatViewClickedUrl_.protocol() != "kmess" || chatViewClickedUrl_.host() != "application" )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "ChatView::slotSendAppCommand() - Not sending invalid application link: " << chatViewClickedUrl_.url() << endl;
#endif

    return;
  }

/*
 * KMess' internal application URLs are in the form
 * kmess://application/responseType/accountHandle?cookieId
 */

  QString path  = chatViewClickedUrl_.path().mid( 1 );  // remove first /
  QString query = chatViewClickedUrl_.query().mid( 1 ); // Remove the ?

  QString method  = path.section( "/", 0, 0 );   // First parameter: response type
  QString contact = path.section( "/", 1, 1 );   // Second parameter: contact handle
  QString cookie  = query;                       // Third parameter: transfer cookie ID

#ifdef KMESSTEST
  ASSERT( ! path.isEmpty() );
  ASSERT( ! query.isEmpty() );
#endif

#ifdef KMESSDEBUG_CHATVIEW
  kdDebug() << "ChatView::slotSendAppCommand() - Sending application link - method=" << method << " contact=" << contact << " cookie=" << cookie << endl;
#endif

  emit appCommand( cookie, contact, method );
}



// The user right clicked at the KHTMLPart to show a popup.
void ChatView::slotShowContextMenu( const QString &clickedUrl, const QPoint &point )
{
  KAction *urlAction = 0;

  // Add items to this context menu
  KPopupMenu *contextMenu = new KPopupMenu( this );

  // Analyze incoming URL, if present
  if( ! clickedUrl.isEmpty() )
  {
    KURL url( clickedUrl );

#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "ChatView::slotShowContextMenu() - Clicked URL: " << url.prettyURL() << endl;
#endif

    if( url.protocol() == "kmess" && url.host() == "emoticon" )
    {
      urlAction = new KAction( i18n("Add this &Emoticon..."),  "add",           0, this, SLOT(slotAddNewEmoticon()),  this, "addemoticon"  );
      urlAction->plug( contextMenu );
    }
    else if( url.protocol().left( 6 ) == "mailto" )
    {
      urlAction = new KAction( i18n("Send &Email"),            "mail_generic",  0, this, SLOT(slotVisitAddress()),    this, "sendemail"    );
      urlAction->plug( contextMenu );
      urlAction = new KAction( i18n("Copy E&mail"),            "copy",          0, this, SLOT(slotCopyAddress()),     this, "copyemail"    );
      urlAction->plug( contextMenu );
    }
    else
    {
      urlAction = new KAction( i18n("Visit &Link"),            "launch",        0, this, SLOT(slotVisitAddress()),    this, "visiturl"     );
      urlAction->plug( contextMenu );
      urlAction = new KAction( i18n("Copy &Address"),          "copy",          0, this, SLOT(slotCopyAddress()),     this, "copyurl"      );
      urlAction->plug( contextMenu );
    }

    chatViewClickedUrl_ = url;
  }

  // Create items
  KAction *copyAction       = KStdAction::copy( this, SLOT(slotCopyChatText()), 0 );  // , actionCollection() );
  KAction *selectAllAction  = KStdAction::selectAll( this, SLOT(slotSelectAllChatText()), 0 ); // , actionCollection() );
  KAction *saveToFileAction = KStdAction::save( this, SLOT(showSaveChatDialog()), 0 );  // , actionCollection() );

  // Update the labels a bit though
  copyAction      ->setText( i18n("&Copy text") );
  selectAllAction ->setText( i18n("Select &All") );
  saveToFileAction->setText( i18n("Save chat to &File") );

  // Add a separator to divide the context-depending entries from the rest of the menu
  if( urlAction != 0 )
  {
    contextMenu->insertSeparator();
  }

  copyAction->plug( contextMenu );
  selectAllAction->plug( contextMenu );

  contextMenu->insertSeparator();
  saveToFileAction->plug( contextMenu );

  // Set items disabled, depending on the text selection
  if( chatMessageView_->hasSelection() )
  {
    saveToFileAction->setEnabled( false );
  }
  else
  {
    copyAction->setEnabled( false );
  }

  // Show the menu
  contextMenu->exec( point );
  delete contextMenu;
  delete copyAction;
}



// The user clicked the "visit address" or "send email" option in the context menu, or clicked a link in the ChatMessageView
void ChatView::slotVisitAddress()
{
  QString url = chatViewClickedUrl_.url();

  if( ! chatViewClickedUrl_.isValid() )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kdDebug() << "ChatView::slotVisitAddress() - Not opening invalid URL: " << url << endl;
#endif

    return;
  }

#ifdef KMESSDEBUG_CHATVIEW
  kdDebug() << "ChatView::slotVisitAddress() - Opening URL: " << url << endl;
#endif

  new KRun( url );

  // Reset the url
  chatViewClickedUrl_ = KURL();
}



// Update the editor's font to match the account's font
void ChatView::updateEditorFont()
{
#ifdef KMESSTEST
  ASSERT( currentAccount_ != 0 );
#endif
  QColor color;

  if( currentAccount_ == 0 )
  {
    return;
  }

  // Change the color to the user's color
  color.setNamedColor( currentAccount_->getFontColor() );
  messageEdit_->setColor( color );
  messageEdit_->setPaletteForegroundColor( color );
  messageEdit_->setFont( currentAccount_->getFont() );
  messageEdit_->setCurrentFont( currentAccount_->getFont() );
  messageEdit_->setPointSize( currentAccount_->getFont().pointSize() );

  // If the contact font settings changed, regenerate the messages.
  if( chatStyle_ != 0 )
  {
    if( currentAccount_->getUseContactFont()   != chatStyle_->getUseContactFont()
    ||  currentAccount_->getContactFont()      != chatStyle_->getContactFont()
    ||  currentAccount_->getContactFontColor() != chatStyle_->getContactFontColor() )
    {
      updateChatStyle();
    }
  }
}



// Update the chat style
void ChatView::updateChatStyle()
{
#ifdef KMESSDEBUG_CHATVIEW
  kdDebug() << "ChatView::updateChatStyle: updating style." << endl;
#endif

  if(KMESS_NULL(chatStyle_))      return;
  if(KMESS_NULL(currentAccount_)) return;

  // Load new settings in the chat style object
  chatStyle_->setStyle           ( currentAccount_->getChatStyle()        );
  chatStyle_->setContactFont     ( currentAccount_->getContactFont()      );
  chatStyle_->setContactFontColor( currentAccount_->getContactFontColor() );
  chatStyle_->setShowTime        ( currentAccount_->getShowMessageTime()  );
  chatStyle_->setUseContactFont  ( currentAccount_->getUseContactFont()   );
  chatStyle_->setUseEmoticons    ( currentAccount_->getUseEmoticons()     );
  chatStyle_->setUseFontEffects  ( currentAccount_->getUseFontEffects()   );

  // Support grouping of follow-up messages.
  bool groupFollowups = currentAccount_->getGroupFollowupMessages();
  lastContactMessages_.clear();

  // Replace the entire contents with the new style.
  QString newHtml;
  QPtrListIterator<ChatMessage> it(chatMessages_);
  while( it.current() != 0 )
  {
    ChatMessage *chatMessage = it.current();

    if( groupFollowups && chatMessage->isNormalMessage() )
    {
      // See if the message should be added to the queue.
      // There could be another follow-up message later.
      if( ! lastContactMessages_.isEmpty() )
      {
        // Check whether the contact differs or it's a different type,
        // the handle to difference between "offline_incoming" and "incoming" messages.
        ChatMessage *lastMessage = lastContactMessages_.last();
        if( lastMessage->getType()          != chatMessage->getType()
        ||  lastMessage->getContactHandle() != chatMessage->getContactHandle() )
        {
          // New mesage should not be added to queue,
          // so previous mesage was last the one. Flush the queue.
          newHtml += "\n<div class=\"messageContainer messageListContainer\">"
                  + chatStyle_->convertMessageList(lastContactMessages_)
                  + "</div>\n";
          lastContactMessages_.clear();
        }
      }

      // Append new contact message to the queue.
      lastContactMessages_.append(chatMessage);
    }
    else
    {
      // Not a contact message.
      // First flush the queue, then add the new message.
      if( ! lastContactMessages_.isEmpty() )
      {
        newHtml += "\n<div class=\"messageContainer messageListContainer\">"
                 + chatStyle_->convertMessageList(lastContactMessages_)
                 + "</div>\n";
        lastContactMessages_.clear();
      }

      // Convert the current message.
      newHtml += "\n<div class=\"messageContainer\">"
               + chatStyle_->convertMessage(*chatMessage)
               + "</div>\n";
    }
    ++it;
  }

  QString messageRoot = chatStyle_->convertMessageRoot();
  if( messageRoot.isNull() )
  {
    // Assign standard HTML
    chatMessageView_->setStandardHtml( newHtml, chatStyle_->getCssFile(), chatStyle_->getBaseFolder() );
  }
  else
  {
    // Assign the custom HTML root
    chatMessageView_->setHtml( messageRoot, newHtml );
  }

  // If there are still grouped messages, add them after setHtml().
  // This allows the chatMessageView_ to replace them later with replaceLastMessage();
  if( ! lastContactMessages_.isEmpty() )
  {
    chatMessageView_->addHtmlMessage( chatStyle_->convertMessageList(lastContactMessages_) );
    // Don't clear queue here, so replaceLastMessage() will be called.
  }

  // Call the scroll function a bit later,
  // so Qt/kde get a chance to update the height before the scrolling starts.
  QTimer::singleShot(50, chatMessageView_, SLOT(scrollChatToBottom()));
}



// Update the messages which contain custom emoticons
void ChatView::updateCustomEmoticon( const QString &handle, const QString &code )
{
#ifdef KMESSTEST
  ASSERT( ! code.isEmpty() );
#endif

  // Check for empty replacements.
  if( code.isEmpty() )
  {
    kdWarning() << "ChatView: can't update custom emoticon, emoticon code not given (contact=" << handle << ")." << endl;
    return;
  }

  // Get contact emoticon replacements.
  const ContactBase *contact = currentAccount_->getContactByHandle(handle);
  if(KMESS_NULL(contact)) return;

  // Get emoticon replacement, instruct chatMesasgeView to replace it.
  const QString &replacement = contact->getEmoticonReplacements()[code];
  chatMessageView_->updateCustomEmoticon( code, replacement, handle, pendingEmoticonTags_ );
}


#include "chatview.moc"


Generated by  Doxygen 1.6.0   Back to index