Logo Search packages:      
Sourcecode: kmess version File versions

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


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



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



// 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,                 SLOT( slotOpenURLRequest(const KURL&, const KParts::URLArgs&) ) );

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

  // Disable drag&drop for now..
  view()->setAcceptDrops(false);
}



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



// Open a new url in the khtml widget
void ChatMessageView::slotOpenURLRequest(const KURL &url, const KParts::URLArgs & /* args */)
{
  if( url.protocol() == "kmess" )
  {
#ifdef KMESSTEST
    ASSERT( url.path().left(1) == "/" );
#endif

    QString method  = url.host();
    QString contact = url.path().mid(1);  // remove first /
    QString cookie  = url.query().mid( 1 );

#ifdef KMESSDEBUG_CHATMESSAGEVIEW
    kdDebug() << "ChatMessageView::slotOpenURLRequest( url=" << url << " )" << endl;
    kdDebug() << "ChatMessageView: method=" << method << " contact=" << contact << " cookie=" << cookie << endl;
#endif

    emit appCommand( cookie, contact, method );
  }
  else
  {
#ifdef KMESSDEBUG_CHATMESSAGEVIEW
    kdDebug() << "ChatMessageView: Opening URL: " << url << endl;
#endif
    new KRun(url);
  }
}



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

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

    // Get element, see if it matches the code
    DOM::HTMLElement img = document.getElementById(*it);
    if( img.isNull() || img.getAttribute("alt") != code )
    {
      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) );
      attribPos += attribRegExp.matchedLength();
    }
  }
}


#include "chatmessageview.moc"

Generated by  Doxygen 1.6.0   Back to index