/***************************************************************************
                          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 "../contact/contact.h"
#include "../dialogs/addemoticondialog.h"
#include "../model/contactlist.h"
#include "../utils/kmessshared.h"
#include "../currentaccount.h"
#include "../accountsmanager.h"
#include "../emoticonmanager.h"
#include "../kmess.h"
#include "../kmessapplication.h"
#include "contactswidget.h"

#include <IsfQt>
#include <IsfInkCanvas>

#include <QClipboard>
#include <QDropEvent>
#include <QEvent>
#include <QRegExp>
#include <QTemporaryFile>
#include <QTextCodec>
#include <QTextDocument>
#include <QTextOption>

#include <KAction>
#include <KFileDialog>
#include <KHTMLView>
#include <KLocale>
#include <KMenu>
#include <KMessageBox>
#include <KRun>
#include <KStandardAction>



// The constructor
ChatView::ChatView( QWidget *parent )
: QWidget( parent )
// , Ui::ChatView()
, contactsWidget_( 0 )
, currentAccount_( 0 )
, initialized_( false )
{
  // Insert a KHTMLPart in the placeholder
  chatMessageView_ = new ChatMessageView( this, this );
  connect( chatMessageView_, SIGNAL(                popupMenu(const QString&,const QPoint&)  ),
           this,             SLOT  (      slotShowContextMenu(const QString&,const QPoint&)  ) );
  connect( chatMessageView_, SIGNAL(           openUrlRequest(const KUrl&)                   ),
           this,               SLOT(       slotOpenURLRequest(const KUrl&)                   ) );
  connect( this,             SIGNAL( updateApplicationMessage(const QString&,const QString&) ),
           chatMessageView_,   SLOT( updateApplicationMessage(const QString&,const QString&) ) );

  // Create a layout to maximize the KHTMLPart
  QVBoxLayout *layout = new QVBoxLayout( this );
  layout->setContentsMargins( 0, 6, 0, 0 );
  layout->addWidget( chatMessageView_->view() );

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

  // Create the containers for this chat's current text and drawing
  messageEditContents_ = new QTextDocument();
  inkDrawing_          = new Isf::Drawing();

  // Adapt the new document for RTL is necessary
  if( QApplication::isRightToLeft() )
  {
    QTextOption textOption;
    textOption.setTextDirection( Qt::RightToLeft );
    messageEditContents_->setDefaultTextOption( textOption );
    messageEditContents_->clear();
  }
}



// The destructor
ChatView::~ChatView()
{
  // Delete the related widgets
  delete inkDrawing_;
  delete messageEditContents_;
}



// Parse and add the given XML string to the view
void ChatView::addContents( const QString& xmlString )
{
  chatMessageView_->addXml( xmlString );
}


// Copy the currently selected text
void ChatView::editCopy()
{
  // The view never has focus, only selection.
  if( ! chatMessageView_->hasSelection() )
  {
    return;
  }

  kapp->clipboard()->setText( chatMessageView_->selectedText() );
  // use selectedTextAsHTML() and setData() to copy HTML instead of text
}



// 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 )
{
  Q_UNUSED( obj );

  switch( event->type() )
  {
    case QEvent::DragEnter:
    case QEvent::DragMove:
    {
      QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>( event );

      if( ! dragEvent )
      {
#ifdef KMESSDEBUG_CHATVIEW
          kmDebug() << "Ignoring invalid drag event!";
#endif
        return false;
      }

      const QMimeData *mimeData = dragEvent->mimeData();
      if( mimeData->hasUrls()
      ||  mimeData->hasImage()
      ||  mimeData->hasFormat( "application/kmess.list.item" ) )
      {
#ifdef KMESSDEBUG_CHATVIEW
        kmDebug() << "Accepting drag event of mimetypes:" << dragEvent->mimeData()->formats();
#endif
        dragEvent->acceptProposedAction();
        return true;
      }

#ifdef KMESSDEBUG_CHATVIEW
      kmDebug() << "Ignoring invalid drop having mimetypes:" << dragEvent->mimeData()->formats();
#endif
      break;
    }

    case QEvent::Drop:
    {
      QDropEvent *dropEvent = static_cast<QDropEvent*>( event );
      const QMimeData *data = dropEvent->mimeData();

      // Process the drop only if it comes from out of our application.
      if( ! dropEvent || dropEvent->source() != 0 )
      {
#ifdef KMESSDEBUG_CHATVIEW
        kmDebug() << "Ignoring invalid drop!";
#endif
        break;
      }

      const QMimeData *mimeData = dropEvent->mimeData();
      if( mimeData->hasUrls() )
      {
#ifdef KMESSDEBUG_CHATVIEW
        kmDebug() << "Drag'n'dropped files:" << mimeData->urls();
#endif

        // Send the files to the contact
        startFileTransfer( mimeData->urls() );

        dropEvent->acceptProposedAction();
        return true;
      }

      if( mimeData->hasImage() )
      {
        QImage image( qvariant_cast<QImage>( mimeData->imageData() ) );

        if( image.isNull() )
        {
#ifdef KMESSDEBUG_CHATVIEW
          kmDebug() << "Drag'n'dropped an invalid image!";
#endif
          return true;
        }

#ifdef KMESSDEBUG_CHATVIEW
        kmDebug() << "Drag'n'dropped a" << image.size() << "image";
#endif

        // Create a temporary file where the image will be stored.
        // TODO: The file is not destroyed, as it needs to be kept
        // available until the transfer is completed. Find a way
        // to remove it when KMess quits or as soon as the transfer
        // is done.
        QTemporaryFile file;
        file.setAutoRemove( false );
        file.setFileTemplate( QDir::tempPath() + "/kmess.XXXXXX.jpg" );

        if( ! file.open() )
        {
#ifdef KMESSDEBUG_CHATVIEW
          kmDebug() << "Cannot create temporary image file!";
#endif
          return true;
        }

        dropEvent->acceptProposedAction();

        // Save the image and get its local URL
        image.save( &file, "JPG" );
        file.close();
        QUrl imageUrl( "file://" + file.fileName() );

        // Send the image to the contact
        QList<QUrl> urlList;
        urlList.append( imageUrl );
        startFileTransfer( urlList );

        return true;
      }

      if(   data->hasFormat( "application/kmess.list.item" )
      &&  ! data->text().isEmpty() )
      {
        // Invite the listed contacts, but check them a little
        QStringList handles( data->text().split( "; " ) );

        foreach( const QString &handle, handles )
        {
          // Don't try to invite random strings, nonexistent handles
          if( ! Account::isValidEmail( handle )
          ||    currentAccount_->getContactByHandle( handle ) == 0 )
          {
            handles.removeAll( handle );
          }
        }

        // If any address has survived, it has gained itself the valhalla, an invitation to our chat
        if( ! handles.isEmpty() )
        {
#ifdef KMESSDEBUG_CHATVIEW
          kmDebug() << "Drag'n'dropped addresses:" << handles;
#endif
          inviteContacts( handles );
          dropEvent->acceptProposedAction();
          return true;
        }
      }

      break;
    }

    // Ensure that the chat shows the same contents when resizing it
    case QEvent::Resize:
    {
      chatMessageView_->scrollChatToBottomGently();
      break;
    }

    default:
      break;
  }

  return false;
}



// Return a pointer to this chat's Contacts Widget
ContactsWidget *ChatView::getContactsWidget() const
{
  return contactsWidget_;
}



// Return a pointer to the ink image drawn by the user in this chat
Isf::Drawing *ChatView::getInkDrawing() const
{
  return inkDrawing_;
}



// Return a pointer to the text typed by the user in this chat
QTextDocument *ChatView::getMessageEditContents() const
{
  return messageEditContents_;
}



// Get the text zoom factor
int ChatView::getZoomFactor()
{
  return chatMessageView_->zoomFactor();
}



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

  if ( initialized_ )
  {
    kmDebug() << "already initialized.";
    return false;
  }

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

  connect( currentAccount_, SIGNAL(      changedFontSettings() ),
           chatMessageView_, SLOT (          updateChatStyle() ) );
  connect( currentAccount_, SIGNAL( changedChatStyleSettings() ),
           chatMessageView_, SLOT (          updateChatStyle() ) );
  connect( currentAccount_, SIGNAL(  changedEmoticonSettings() ),
           chatMessageView_, SLOT (          updateChatStyle() ) );

  // Create the widget
  contactsWidget_ = new ContactsWidget( 0 );

  // Hide it by default
  contactsWidget_->hide();

  // Update the chat view with the style settings
  // from the current account
  chatMessageView_->updateChatStyle();

  initialized_ = true;
  return true;
}



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



/* Save the chat to the given file
 *
 * @param path The path to the file to save to
 * @param format Format of the file (xml, text, ...)
 * @param overwriteContents whether to append or overwrite the contents of the file
 * @param allowUserInteraction whether to allow dialog boxes to be sent if something succeeds or fails
 * @returns true if everything was saved correctly, false otherwise
 */
bool ChatView::saveChatToFile( const QString &path, Account::ChatExportFormat format, bool overwriteContents, bool allowUserInteraction )
{
#ifdef KMESSDEBUG_CHATVIEW
  kmDebug() << "saving chat to '" << path << "'.";
#endif

  // Find out if the file already exists
  QFile file( path );
  bool isExistingFile = file.exists();

  // Create and open the file.
  if( ! file.open( QIODevice::ReadWrite ) )
  {
    if(  ! allowUserInteraction )
    {
      return false;
    }

    QFileInfo info( file );
    kmWarning() << "File save failed! Could not open file" << path << ".";
    KMessageBox::sorry( chatMessageView_->view(), i18n( "Could not save chat log in directory '%1'.\n"
                                    "Make sure you have permission to write in "
                                    "the folder where logs are being saved.",
                                    info.absolutePath() ) );
    return false;
  }

  // Add a bit more security to the log file, if the platform supports it
  file.setPermissions( QFile::ReadOwner | QFile::WriteOwner );

  QString fileData;
  QString appendPoint;
  bool willAppend = ( isExistingFile && ! overwriteContents );

  // Search for the style tag: we will append to this file only if the
  // tag exists and is identical to the current tag.
  // Text chat logs don't have any formatting nor tag, so this test must
  // be skipped.
  if( willAppend && format != Account::EXPORT_TEXT )
  {
    fileData = file.read( 512 );
    if( fileData.indexOf( chatMessageView_->getStyleTag() ) == -1 )
    {
      kmWarning() << "Style Tag not found in data:" << fileData << ", writing to a new file";

      // we return false, so the caller will write this data to a new file
      return false;
    }
  }

  QString chatHistory( chatMessageView_->getHistory( format, willAppend, appendPoint ) );

#ifdef KMESSDEBUG_CHATVIEW
  kmDebug() << "Writing to file:" << file.fileName();
  kmDebug() << "Current file exists?" << isExistingFile << " allow overwriting?" << overwriteContents;
  kmDebug() << "Will append to current file?" << willAppend;
#endif

  if( willAppend )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kmDebug() << "Appending to existing chat file...";
#endif

    const qint16 maxReadSize = 256;

    // Read the last bytes of the file
    file.seek( file.size() - maxReadSize );
    fileData = file.read( maxReadSize );
    int pos = fileData.lastIndexOf( appendPoint );

    // The append point was not found!
    if( pos < 0 )
    {
#ifdef KMESSDEBUG_CHATVIEW
      kmDebug() << "Append process failed, insertion string not found.";
#endif
      return false;
    }

    // Seek the file to the insertion point
    file.seek( file.size() - maxReadSize + pos );
    // Add to the chat history the current 'tail' of the file
    chatHistory += fileData.mid( pos );
    // Insert the history and tail in the file, below
  }
  else
  {
#ifdef KMESSDEBUG_CHATVIEW
    kmDebug() << "Creating new chat file...";
#endif
    file.resize( 0 ); // Truncate it (99% useless precaution)
  }

  // Output the chat history to the file, with the right text encoding
  QTextStream textStream( &file );

  // For plain text logs we use the locale codec
  if (format == Account::EXPORT_TEXT)
  {
    textStream.setCodec( QTextCodec::codecForLocale() );
  }
  // For HTML logs (which are created with the UTF-8 charset) we use UTF-8 encoding
  else
  {
    textStream.setCodec( QTextCodec::codecForName("UTF-8") );
  }

  textStream << chatHistory;
  textStream.flush();
  file.close();

  return true;
}



// Enable or disable the parts of the chat which allow user interaction
void ChatView::setEnabled( bool isEnabled )
{
  // Set the state of the main UI elements
  contactsWidget_->setEnabled( isEnabled );

  // The chat is being enabled back, replace some stuff which changes
  // at every connection
  if( isEnabled )
  {
    // Re-read the current account pointer
    // because it likely is a different one
    currentAccount_ = CurrentAccount::instance();
  }
}



// Replace the message browser's contents with the given XML string.
void ChatView::setContents( const QString& xmlString )
{
  chatMessageView_->setXml( xmlString );
}



// Set the zoom factor of the text
void ChatView::setZoomFactor( int percentage )
{
  chatMessageView_->setFontScaleFactor( percentage );
}



// Add the given message to the message browser.
void ChatView::showMessage( const ChatMessage &message )
{
  // Send it to the message view to have it shown
  chatMessageView_->showMessage( message );

  // Notify the contacts widget, so that it updates the right contact frame
  if( ! KMESS_NULL( contactsWidget_ ) )
  {
    contactsWidget_->messageReceived( message.getContactHandle() );
  }
}



// Show a dialog to save the chat.
void ChatView::showSaveChatDialog()
{
  QFileInfo targetFile;
  int       overwriteAllowed;

  // Default name for the saved chat
  QString path( currentAccount_->getSaveChatPath() + "/kmess-chat.html" );

  // 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,
                                         i18nc( "Chat log saving dialog, file type filter",
                                                "*.html *.htm|Web Page (*.html)\n"
                                                "*.txt|Plain Text Document (*.txt)\n" ) );

    // 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?", targetFile.fileName() ),
                                i18n( "Overwrite File" ),
                                KGuiItem( i18n( "Over&write" ) ) );
    }

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

  Account::ChatExportFormat format;
  if( targetFile.suffix() == "html" )
  {
    format = Account::EXPORT_HTML;
  }
  else
  {
    format = Account::EXPORT_TEXT;
  }

  saveChatToFile( path, format, true );
}



/**
 * Add a contact's email to the contact list
 */
void ChatView::slotAddContact()
{
  QString email( chatViewClickedUrl_.url() );

  if( ! chatViewClickedUrl_.isValid() )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kmDebug() << "Not adding anything from invalid URL: " << email;
#endif

    return;
  }

#ifdef KMESSDEBUG_CHATVIEW
  kmDebug() << "Adding contact from URL: " << email;
#endif

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

  emit addContact( email );

  // Reset the url
  chatViewClickedUrl_.clear();
}



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

    return;
  }

/*
 * KMess' internal emoticon addition URLs are in the form
 * <code>kmess://emoticon/contactHandle/urlEncodedShortcut/urlEncodedPicturePath</code>
 */

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

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

  // Reset the url
  chatViewClickedUrl_.clear();

  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 path

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

    return;
  }

  // URL-Decode the encoded strings
  picture  = KUrl::fromPercentEncoding( picture.toAscii()  );
  shortcut = KUrl::fromPercentEncoding( shortcut.toUtf8() );

#ifdef KMESSDEBUG_CHATVIEW
  kmDebug() << "Showing Add Emoticon dialog - shortcut=" << shortcut << " picture=" << picture;
#endif

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

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

  addDialog->preSelect( picture, shortcut );

}



// Clear the chat's contents
void ChatView::slotClearChat()
{
  chatMessageView_->clearView( false );
}



// 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
    kmDebug() << "Not copying invalid URL: " << url;
#endif

    return;
  }

#ifdef KMESSDEBUG_CHATVIEW
  kmDebug() << "Copying URL: " << url;
#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_.clear();
}






// The user clicked the "find text" option in the context menu
void ChatView::slotFindChatText()
{
  chatMessageView_->findText();
}



/**
 * Add an emoticon from the chat to the contact's emoticon blacklist.
 *
 * The user can add a contact's emoticons into a black list: KMess will not display
 * blacklisted emoticons, and keep the shortcut as text. Useful for contacts
 * having annoying emoticons.
 */
void ChatView::slotIgnoreEmoticon()
{
  if( ! chatViewClickedUrl_.isValid() || chatViewClickedUrl_.protocol() != "kmess" || chatViewClickedUrl_.host() != "emoticon" )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kmDebug() << "Ignoring request for invalid URL: " << chatViewClickedUrl_;
#endif

    return;
  }

/*
 * KMess' internal emoticon URLs are in the form
 * <code>kmess://emoticon/contactHandle/urlEncodedShortcut/urlEncodedPicturePath</code>
 */

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

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

  // Reset the url
  chatViewClickedUrl_.clear();

  QString handle  ( path.section( "/", 0, 0 ) );   // First parameter: contact handle
  QString shortcut( path.section( "/", 1, 1 ) );   // Second parameter: emoticon shortcut

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

    return;
  }

  // URL-Decode the encoded strings
  shortcut = KUrl::fromPercentEncoding( shortcut.toUtf8() );

  // Add the emoticon to the contact's blacklist and update the chat
  if( contact->manageEmoticonBlackList( true, shortcut ) )
  {
    chatMessageView_->removeCustomEmoticon( shortcut );
  }
}



// Open a new url clicked in the khtml widget
void ChatView::slotOpenURLRequest( const KUrl &url )
{
    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 if( url.host() == "accountconfig" )
      {
        // Show the account config dialog
        AccountsManager::instance()->showAccountSettings(
            currentAccount_, window(), AccountSettingsDialog::PageChatStyle );
      }
    }
    else if( url.protocol() == "file" )
    {
      // Obtain the widget of the main KMess window, to correctly link it to the opened app
      KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp );
      QWidget *mainWindow = kmessApp->getContactListWindow()->window();

      // Execute the local file with the system's default association (KRun auto-deletes itself)
      new KRun( url, mainWindow, 0, true );
    }
    else
    {
      // Just execute the link
      slotVisitAddress();
    }
}



// Scroll the view forward or backward
void ChatView::scrollTo( bool forward, bool fast )
{
  chatMessageView_->scrollChat( forward, fast );
}



// Scroll the view down to the last line
void ChatView::scrollToBottom( bool force )
{
  if( force )
  {
    chatMessageView_->scrollChatToBottom();
  }
  else
  {
    chatMessageView_->scrollChatToBottomGently();
  }
}



// Change ink drawing object
void ChatView::setInkDrawing( Isf::Drawing* newDrawing )
{
  delete inkDrawing_;
  inkDrawing_ = newDrawing;
}





/**
 * The user clicked a kmess internal link in the ChatMessageView
 */
void ChatView::slotSendAppCommand()
{
  // Ignore non-internal links and non-application internal links
  if( ! chatViewClickedUrl_.isValid() || chatViewClickedUrl_.protocol() != "kmess" || chatViewClickedUrl_.host() != "application" )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kmDebug() << "Not sending invalid application link: " << chatViewClickedUrl_.url();
#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
  KMESS_ASSERT( ! path.isEmpty() );
  KMESS_ASSERT( ! query.isEmpty() );
#endif

#ifdef KMESSDEBUG_CHATVIEW
  kmDebug() << "Sending application link - method=" << method << " contact=" << contact << " cookie=" << cookie;
#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
  KMenu *contextMenu = chatMessageView_->popupMenu();

  // separate the "copy/find/select all" actions from the chat-specific ones below.
  contextMenu->addSeparator();

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

#ifdef KMESSDEBUG_CHATVIEW
    kmDebug() << "Clicked URL: " << url.prettyUrl();
#endif

    if( url.protocol() == "kmess" && url.host() == "emoticon" )
    {
      urlAction = new KAction( KIcon( "list-add" ), i18n("Add this &Emoticon..."), this );
      connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotAddNewEmoticon()) );
      contextMenu->addAction( urlAction );
      urlAction = new KAction( KIcon( "list-remove" ), i18n("Hide this &Emoticon"), this );
      connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotIgnoreEmoticon()) );
      contextMenu->addAction( urlAction );
    }
    else if( url.protocol().left( 6 ) == "mailto" )
    {
      urlAction = new KAction( KIcon( "mail" ), i18n("Send &Email"), this );
      connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotVisitAddress()) );
      contextMenu->addAction( urlAction );

      urlAction = new KAction( KIcon( "list-add-user" ), i18n("Add &Contact"), this );
      connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotAddContact()) );
      contextMenu->addAction( urlAction );

      urlAction = new KAction( KIcon( "copy" ), i18n("Copy E&mail Address"), this );
      connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotCopyAddress()) );
      contextMenu->addAction( urlAction );
    }
    else
    {
      urlAction = new KAction( KIcon( "document-open-remote" ), i18n("Visit &Link"), this );
      connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotVisitAddress()) );
      contextMenu->addAction( urlAction );

      urlAction = new KAction( KIcon( "edit-copy" ), i18n("Copy &Address"), this );
      connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotCopyAddress()) );
      contextMenu->addAction( urlAction );
    }

    chatViewClickedUrl_ = url;
  }

#ifdef KMESSDEBUG_CHATVIEW
  kmDebug() << "Creating popup menu entries";
#endif

  // Create items
  KAction *clearChatAction  = KStandardAction::clear( this, SLOT(slotClearChat()), 0 );
  KAction *saveToFileAction = KStandardAction::save( this, SLOT(showSaveChatDialog()), 0 );

  // Update the labels a bit though
  clearChatAction ->setText( i18n("C&lear Chat") );
  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->addSeparator();
  }
  contextMenu->addAction( clearChatAction );
  contextMenu->addAction( saveToFileAction );

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

  // Only enable the clear chat action if it contains messages
  clearChatAction->setEnabled( ! isEmpty() );

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

  delete contextMenu;
  delete clearChatAction;
  delete saveToFileAction;
  if ( urlAction != 0 )
  {
    delete urlAction;
  }

}



// The user clicked the "visit address" or "send email" option in the context menu, or clicked a link in the ChatMessageView
void ChatView::slotVisitAddress()
{
  if( ! chatViewClickedUrl_.isValid() )
  {
#ifdef KMESSDEBUG_CHATVIEW
    kmDebug() << "Not opening invalid URL: " << chatViewClickedUrl_;
#endif

    return;
  }

#ifdef KMESSDEBUG_CHATVIEW
  kmDebug() << "Opening URL: " << chatViewClickedUrl_;
#endif

  // Launch the browser for the given URL
  if( chatViewClickedUrl_.protocol() == "mailto" )
  {
    currentAccount_->openMailAtCompose( chatViewClickedUrl_.url().mid( 7 ) );
  }
  else
  {
    KMessShared::openBrowser( chatViewClickedUrl_ );
  }

  // Reset the url
  chatViewClickedUrl_.clear();
}



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

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

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

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



#include "chatview.moc"

