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

chatmessageview.cpp

/***************************************************************************
                          chatmessageview.cpp -  description
                             -------------------
    begin                : Sat Nov 8 2005
    copyright            : (C) 2005 by Diederik van der Boor
    email                : "vdboor" --at-- "codingdomain.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 "chatmessageview.h"

#include "../kmessdebug.h"

#include <qstringlist.h>
#include <qstylesheet.h>

#include <klocale.h>
#include <krun.h>
#include <kstddirs.h>

#include <khtml_part.h>
#include <khtmlview.h>
#include <kparts/browserextension.h>
#include <dom/dom_doc.h>
#include <dom/dom_text.h>
#include <dom/dom_element.h>
#include <dom/html_base.h>
#include <dom/html_document.h>
#include <dom/html_inline.h>

#include <qdragobject.h>
#include <qstringlist.h>

#include "../dialogs/addemoticondialog.h"
#include "../emoticonmanager.h"
#include "../emoticontheme.h"

// The constructor
ChatMessageView::ChatMessageView(QWidget *parent, const char *name)
 : KHTMLPart(parent, name)
 , isEmpty_(true)
 , lastMessageId_(0)
{
  initialize();
}



// The destructor
ChatMessageView::~ChatMessageView()
{
}



/**
 * Remove from the chat all links to add an emoticon
 *
 * This method is called when you've added a custom emoticon that a contact has sent you.
 * It searches the chat for the emoticon you have added; and removes the "add this emoticon"
 * link from all the occurrences of it.
 * This way, you can't add twice an emoticon. This behavior mimicks that of MSN/Windows Live
 * Messenger.
 */
00073 void ChatMessageView::addedEmoticon( QString shortcut )
{
#ifdef KMESSDEBUG_CHATMESSAGEVIEW
  kdDebug() << "ChatMessageView::addedEmoticon() - Replacing add emoticon links with shortcut '" << shortcut << "'." << endl;
#endif

  // The shortcut in emoticon links is url-encoded, convert the original one to match it
  shortcut = KURL::encode_string( shortcut );

  DOM::HTMLElement link, parent, image;
  DOM::HTMLDocument document = htmlDocument();

  // Find all the list's links which point to this emoticon
  DOM::NodeList linksList = document.getElementsByName( "newEmoticon_" + shortcut );

  if( linksList.isNull() )
  {
    return;
  }

  // Check all the links in the list. The search is done backwards, because when we delete one of the items
  // of the list, the list itself shortens down reassigning the indices, and a regular loop would fail
  for( long i = (linksList.length() - 1); i >= 0; i-- )
  {
    link = linksList.item( i );
    if( link.isNull() || ! link.isHTMLElement() )
    {
#ifdef KMESSDEBUG_CHATMESSAGEVIEW
      kdDebug() << "ChatMessageView::addedEmoticon() - Link null, skipping." << endl;
#endif
      continue;
    }

    // Replace the link with its first child (the emoticon image)
    parent = link.parentNode();
    image = link.firstChild();

    if( parent.isNull() )
    {
#ifdef KMESSDEBUG_CHATMESSAGEVIEW
      kdDebug() << "ChatMessageView::addedEmoticon() - Parent null, skipping." << endl;
#endif
      continue;
    }

    if( image.isNull() )
    {
#ifdef KMESSDEBUG_CHATMESSAGEVIEW
      kdDebug() << "ChatMessageView::addedEmoticon() - Image null, skipping." << endl;
#endif
      continue;
    }

    parent.replaceChild( image, link );
  }
}



// Add the given html to the chat browser and scroll to the end
void ChatMessageView::addHtmlMessage(const QString &text)
{
  lastMessageId_++;

  // Create new HTML node
  DOM::HTMLElement newNode = document().createElement("div");
  newNode.setAttribute( "id", "message" + QString::number(lastMessageId_) );
  newNode.setAttribute( "class", "messageContainer" );
  newNode.setInnerHTML( text );

  DOM::HTMLElement messageRoot = htmlDocument().getElementById("messageRoot");
  if( messageRoot.isNull() )
  {
    messageRoot = htmlDocument().body();
  }
  messageRoot.appendChild( document().createTextNode("\n") );
  messageRoot.appendChild( newNode );
  messageRoot.appendChild( document().createTextNode("\n") );

  isEmpty_ = false;

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



// Return the HTML source of the page.
QString ChatMessageView::getHtml() const
{
  // TODO: toHTML() Is deprecated, but documentSource() is only present in KDE 3.4
  return htmlDocument().toHTML();
}



// Initialize the widget
void ChatMessageView::initialize()
{
  // Disable features that might do harm
  setJScriptEnabled(false);
  setJavaEnabled(false);
  setMetaRefreshEnabled(false);
  setOnlyLocalReferences(false);   // enable for winks
  setPluginsEnabled(true);         // enable for winks

  // Load a standard empty HTML page.
  setStandardHtml(QString::null);

  // Connect signals for browsing
  connect( browserExtension(), SIGNAL(  openURLRequest(const KURL&, const KParts::URLArgs&) ),
           this,               SIGNAL(  openURLRequest(const KURL&, const KParts::URLArgs&) ) );

  // Make sure this widget can't get any focus.
  view()->setFocusPolicy( QWidget::NoFocus );
}


// Whether or not the message area is empty
bool ChatMessageView::isEmpty() const
{
  return isEmpty_;
}



// Replace the last message with a new contents.
void ChatMessageView::replaceLastMessage(const QString &text)
{
  // Fetch the HTML node
  QString lastId = "message" + QString::number(lastMessageId_);
  DOM::HTMLElement lastNode = htmlDocument().getElementById(lastId);
  if( lastNode.isNull() || ! lastNode.isHTMLElement() )
  {
    kdWarning() << "ChatMessageView::replaceLastMessage: message block with id='" + lastId + "' not found, appending message instead." << endl;
    addHtmlMessage(text);
    return;
  }

  // Replace contents
  lastNode.setInnerHTML(text);

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



// Scroll to the bottom of the chat browser
void ChatMessageView::scrollChatToBottom()
{
  // Make sure the browser scrolls to the bottom
  view()->scrollBy( 0, view()->contentsHeight() );
}



// Replace the entire contents with new HTML code
void ChatMessageView::setHtml(const QString &htmlRoot, const QString &htmlBody)
{
  // Write root elements
  begin();
  write(htmlRoot);

  // Also insert the given HTML body. 
  DOM::HTMLElement messageRoot = document().getElementById("messageRoot");
  if( messageRoot.isNull() )
  {
    kdWarning() << "chat style does not define the 'messageRoot' element." << endl;
    messageRoot = htmlDocument().body();
  }
  messageRoot.setInnerHTML( "\n" + htmlBody + "\n" );

  // Complete loading
  end();

  // Reset state variables.
  lastMessageId_ = 0;
}


// Replace the body contents with new HTML
void ChatMessageView::setStandardHtml(const QString &htmlBody, const QString &cssFile, const QString &baseFolder)
{
  // no: htmlDocument().body().setInnerHTML(html);
  // Write new contents and css links all at once. document().addStyleSheet() is supported since KDE 3.4.

  // include import if css file is given.
  QString cssImport = (cssFile.isNull()    ? QString::null : "\n\n    @import \"" + cssFile + "\";\n");
  QString baseHref  = (baseFolder.isNull() ? QString::null : "  <base href=\"" + baseFolder + "\" id=\"baseHrefTag\">\n");

  // Force standard colors, because chat messages will not work
  // correctly with the (dark) color scheme anyway.

  begin();
  write( "<html id=\"ChatMessageView\"><head>\n"
      "  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + encoding() + "\">\n"
      + baseHref +
      "  <style type=\"text/css\">\n"
      "    /* standard colors for compatibility with dark color schemes */\n"
      "    body      { font-size: 10pt; margin: 0; padding: 5px; background-color: #fff; color: #000; }\n"
      "    a:link    { color: blue; }\n"
      "    a:visited { color: purple; }\n"
      "    a:hover   { color: red; }\n"
      "    a:active  { color: red; }\n"
      + cssImport +
      "  </style>\n"
      "</head>\n"
      "<body>\n"
      "  <div id=\"messageRoot\">\n"
      + htmlBody +
      "  </div>\n"
      "</body>\n"
      "</html>\n" );
  end();

  // Reset state variables.
  lastMessageId_ = 0;
}



// Update an emoticon image placeholder tag with the real replacement.
void ChatMessageView::updateCustomEmoticon( const QString &code, const QString &replacement,
                                            const QString &handle, const QStringList &pendingEmoticonTagIds )
{
#ifdef KMESSDEBUG_CHATMESSAGEVIEW
  kdDebug() << "ChatMessageView: Replacing emoticon '" << code << "' with '" << replacement << "' for '" << handle << "'" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( ! code.isEmpty() );
  ASSERT( ! replacement.isEmpty() );
  ASSERT( ! handle.isEmpty() );
#endif

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

  // The pattern used to find if the new custom emoticon is already in our theme
  QString customEmoticonsPattern = EmoticonManager::instance()->getHtmlPattern( true ).pattern();

  // Process all pending tags, avoid parsing all <img> tags
  DOM::HTMLDocument document = htmlDocument();
  for( QStringList::const_iterator it = pendingEmoticonTagIds.begin(); it != pendingEmoticonTagIds.end(); ++it)
  {
#ifdef KMESSDEBUG_CHATMESSAGEVIEW
    kdDebug() << "ChatMessageView: Checking DOM element '" << *it << "'" << endl;
#endif

    // Check if the element is valid
    DOM::HTMLElement img = document.getElementById(*it);
    if( img.isNull() )
    {
#ifdef KMESSDEBUG_CHATMESSAGEVIEW
      kdDebug() << "ChatMessageView: Skipped null element." << endl;
#endif
      continue;
    }

    // Get the image's alternative text attribute
    QString imageAltText = img.getAttribute("alt").string();

    // Before converting the alt attribute, save it; we'll use it later
    QString originalCode = imageAltText;

    // KHTML's DOM converts all HTML entities to text; so we have to convert them back,
    // to be able to compare the image's code to the emoticon's (which is already encoded)
    imageAltText.replace( "&", "&amp;" )
                .replace( "<", "&lt;" ).replace( ">", "&gt;" )
                .replace( "'", "&#39;" ).replace( '"', "&#34;" );

    // See if this element's ALT attribute matches the shortcut
    if( img.isNull() || imageAltText != code )
    {
#ifdef KMESSDEBUG_CHATMESSAGEVIEW
      kdDebug() << "ChatMessageView: Skipped invalid element (searched '" << code << "', found '" << imageAltText << "')." << endl;
#endif
      continue;
    }

    // Also check whether the handle is also set, avoid replacing someone elses code.
    if( img.getAttribute("contact") != handle )
    {
#ifdef KMESSDEBUG_CHATMESSAGEVIEW
      kdDebug() << "ChatMessageView: Emoticon code found, but different handle: " << img.getAttribute("contact") << endl;
#endif
      continue;
    }


    // Update image placeholder attributes (replacing childs is too much trouble).
    // Create a regexp to parse the replacement attributes.
    QRegExp attribRegExp(
                          "([a-z]+)="       // words followed by an =
                          "(?:"             // start of options
                          "'([^']*)'|"      // attrib separated by single quote, or..
                          "\"([^\"]*)\"|"    // attrib separated by double quote, or..
                          "([^ \t\r\n>]+)"  // attrib followed by space, newline, tab, endtag
                          ")"               // end of options
                         );
#ifdef KMESSTEST
    ASSERT( attribRegExp.isValid() );
#endif

    int attribPos = 0;
    while(true)
    {
      // Find next attribute
      attribPos = attribRegExp.search(replacement, attribPos);
      if( attribPos == -1 )
      {
        break;
      }

#ifdef KMESSDEBUG_CHATMESSAGEVIEW
      kdDebug() << "ChatMessageView: Emoticon replacement has attribute: " << attribRegExp.cap(1) << "=" << attribRegExp.cap(2) << endl;
#endif
      img.setAttribute( attribRegExp.cap(1), attribRegExp.cap(2) );

      // Also change the image's class and reset its ID
      img.setAttribute( "class", "customEmoticon" );
      img.setAttribute( "id", QString::null );

      attribPos += attribRegExp.matchedLength();
    }


    // Allow the user to "steal" this emoticon
    bool isNewEmoticon = ! customEmoticonsPattern.contains( code );
    if( handle != CurrentAccount::instance()->getHandle() && isNewEmoticon )
    {
      // Remove any title in the emoticon image, as it would be displayed instead of the
      // title from the link we're creating (a KHTML issue?)
      img.setAttribute( "title", QString::null );

      // Get the full image tag: we'll get the file name from it when the user clicks on the link
      QString imgTag = img.toHTML();

      // URL-encode the shortcut, so the emoticon adding dialog will get a correct representation,
      // even if it contains characters which would fool the link parser.
      QString urlCode = KURL::encode_string( code );

      // Create a new 'link' element
      // The name attribute is required as, if the user adds the emoticon, we'll want to make all links like this unclickable
      DOM::HTMLElement newNode = document.createElement("a");
      newNode.setAttribute( "name", "newEmoticon_" + urlCode );
      newNode.setAttribute( "title", i18n( "Add this emoticon: %1" ).arg( originalCode ) );
      newNode.setAttribute( "href", "kmess://emoticon/" + handle + "/" + urlCode + "/" + KURL::encode_string( imgTag ) );

      // Set the original image as the link text
      newNode.setInnerHTML( imgTag );

      // And put it in its place
      img.parentNode().replaceChild( newNode, img );
    }
  }

  // Force updating the view to display instantly the new image
  view()->repaintContents();
}


#include "chatmessageview.moc"

Generated by  Doxygen 1.6.0   Back to index