/* kopetemessage.cpp - Base class for Kopete messages Copyright (c) 2002-2003 by Martijn Klingens Copyright (c) 2002-2006 by Olivier Goffart Kopete (c) 2002-2005 by the Kopete developers ************************************************************************* * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * ************************************************************************* */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kopetemessage.h" #include "kopetemetacontact.h" #include "kopeteprotocol.h" #include "kopetechatsession.h" #include "kopeteprefs.h" #include "kopetecontact.h" #include "kopeteemoticons.h" using namespace Kopete; class Message::Private : public TDEShared { public: Private( const TQDateTime &timeStamp, const Contact *from, const ContactPtrList &to, const TQString &subject, MessageDirection direction, const TQString &requestedPlugin, MessageType type ); TQGuardedPtr from; ContactPtrList to; ChatSession *manager; MessageDirection direction; MessageFormat format; MessageType type; TQString requestedPlugin; MessageImportance importance; bool bgOverride; bool fgOverride; bool rtfOverride; bool isRightToLeft; TQDateTime timeStamp; TQFont font; TQColor fgColor; TQColor bgColor; TQString body; TQString subject; }; Message::Private::Private( const TQDateTime &timeStamp, const Contact *from, const ContactPtrList &to, const TQString &subject, MessageDirection direction, const TQString &requestedPlugin, MessageType type ) : from( from ), to( to ), manager( 0 ), direction( direction ), format( PlainText ), type( type ), requestedPlugin( requestedPlugin ), importance( (to.count() <= 1) ? Normal : Low ), bgOverride( false ), fgOverride( false ), rtfOverride( false ), isRightToLeft( false ), timeStamp( timeStamp ), body( TQString() ), subject( subject ) { } Message::Message() : d( new Private( TQDateTime::currentDateTime(), 0L, TQPtrList(), TQString(), Internal, TQString(), TypeNormal ) ) { } Message::Message( const Contact *fromKC, const TQPtrList &toKC, const TQString &body, MessageDirection direction, MessageFormat f, const TQString &requestedPlugin, MessageType type ) : d( new Private( TQDateTime::currentDateTime(), fromKC, toKC, TQString(), direction, requestedPlugin, type ) ) { doSetBody( body, f ); } Message::Message( const Contact *fromKC, const Contact *toKC, const TQString &body, MessageDirection direction, MessageFormat f, const TQString &requestedPlugin, MessageType type ) { TQPtrList to; to.append(toKC); d = new Private( TQDateTime::currentDateTime(), fromKC, to, TQString(), direction, requestedPlugin, type ); doSetBody( body, f ); } Message::Message( const Contact *fromKC, const TQPtrList &toKC, const TQString &body, const TQString &subject, MessageDirection direction, MessageFormat f, const TQString &requestedPlugin, MessageType type ) : d( new Private( TQDateTime::currentDateTime(), fromKC, toKC, subject, direction, requestedPlugin, type ) ) { doSetBody( body, f ); } Message::Message( const TQDateTime &timeStamp, const Contact *fromKC, const TQPtrList &toKC, const TQString &body, MessageDirection direction, MessageFormat f, const TQString &requestedPlugin, MessageType type ) : d( new Private( timeStamp, fromKC, toKC, TQString(), direction, requestedPlugin, type ) ) { doSetBody( body, f ); } Message::Message( const TQDateTime &timeStamp, const Contact *fromKC, const TQPtrList &toKC, const TQString &body, const TQString &subject, MessageDirection direction, MessageFormat f, const TQString &requestedPlugin, MessageType type ) : d( new Private( timeStamp, fromKC, toKC, subject, direction, requestedPlugin, type ) ) { doSetBody( body, f ); } Kopete::Message::Message( const Message &other ) : d(other.d) { } Message& Message::operator=( const Message &other ) { d = other.d; return *this; } Message::~Message() { } void Message::detach() { // there is no detach in TDESharedPtr :( if( d.count() == 1 ) return; // Warning: this only works as long as the private object doesn't contain pointers to allocated objects. // The from contact for example is fine, but it's a shallow copy this way. d = new Private(*d); } void Message::setBgOverride( bool enabled ) { detach(); d->bgOverride = enabled; } void Message::setFgOverride( bool enabled ) { detach(); d->fgOverride = enabled; } void Message::setRtfOverride( bool enabled ) { detach(); d->rtfOverride = enabled; } void Message::setFg( const TQColor &color ) { detach(); d->fgColor=color; } void Message::setBg( const TQColor &color ) { detach(); d->bgColor=color; } void Message::setFont( const TQFont &font ) { detach(); d->font = font; } void Message::doSetBody( const TQString &_body, Message::MessageFormat f ) { TQString body = _body; //TODO: move that in ChatTextEditPart::contents if( f == RichText ) { //This is coming from the RichTextEditor component. //Strip off the containing HTML document body.replace( TQRegExp( TQString::fromLatin1(".*]*>(.*).*") ), TQString::fromLatin1("\\1") ); //Strip

tags body.replace( TQString::fromLatin1("

"), TQString() ); //Replace

with a
body.replace( TQString::fromLatin1("

"), TQString::fromLatin1("
") ); //Remove trailing
if ( body.endsWith( TQString::fromLatin1("
") ) ) body.truncate( body.length() - 5 ); body.remove( TQString::fromLatin1("\n") ); body.replace( TQRegExp( TQString::fromLatin1( "\\s\\s" ) ), TQString::fromLatin1( "  " ) ); } /* else if( f == ParsedHTML ) { kdWarning( 14000 ) << k_funcinfo << "using ParsedHTML which is internal! Message: '" << body << "', Backtrace: " << kdBacktrace() << endl; } */ d->body = body; d->format = f; // unescaping is very expensive, do it only once and cache the result d->isRightToLeft = ( f & RichText ? unescape( d->body ).isRightToLeft() : d->body.isRightToLeft() ); } void Message::setBody( const TQString &body, MessageFormat f ) { detach(); doSetBody( body, f ); } bool Message::isRightToLeft() const { return d->isRightToLeft; } void Message::setImportance(Message::MessageImportance i) { detach(); d->importance = i; } TQString Message::unescape( const TQString &xml ) { TQString data = xml; // Remove linebreak and multiple spaces. First return nbsp's to normal spaces :) data.simplifyWhiteSpace(); int pos; while ( ( pos = data.find( '<' ) ) != -1 ) { int endPos = data.find( '>', pos + 1 ); if( endPos == -1 ) break; // No more complete elements left // Take the part between < and >, and extract the element name from that int matchWidth = endPos - pos + 1; TQString match = data.mid( pos + 1, matchWidth - 2 ).simplifyWhiteSpace(); int elemEndPos = match.find( ' ' ); TQString elem = ( elemEndPos == -1 ? match.lower() : match.left( elemEndPos ).lower() ); if ( elem == TQString::fromLatin1( "img" ) ) { // Replace smileys with their original text' const TQString attrTitle = TQString::fromLatin1( "title=\"" ); int titlePos = match.find( attrTitle, elemEndPos ); int titleEndPos = match.find( '"', titlePos + attrTitle.length() ); if( titlePos == -1 || titleEndPos == -1 ) { // Not a smiley but a normal // Don't update pos, we restart at this position :) data.remove( pos, matchWidth ); } else { TQString orig = match.mid( titlePos + attrTitle.length(), titleEndPos - titlePos - attrTitle.length() ); data.replace( pos, matchWidth, orig ); pos += orig.length(); } } else if ( elem == TQString::fromLatin1( "/p" ) || elem == TQString::fromLatin1( "/div" ) || elem == TQString::fromLatin1( "br" ) ) { // Replace paragraph, div and line breaks with a newline data.replace( pos, matchWidth, '\n' ); pos++; } else { // Remove all other elements entirely // Don't update pos, we restart at this position :) data.remove( pos, matchWidth ); } } // Replace stuff starting with '&' data.replace( TQString::fromLatin1( ">" ), TQString::fromLatin1( ">" ) ); data.replace( TQString::fromLatin1( "<" ), TQString::fromLatin1( "<" ) ); data.replace( TQString::fromLatin1( """ ), TQString::fromLatin1( "\"" ) ); data.replace( TQString::fromLatin1( " " ), TQString::fromLatin1( " " ) ); data.replace( TQString::fromLatin1( "&" ), TQString::fromLatin1( "&" ) ); data.replace( TQString::fromLatin1( " " ), TQString::fromLatin1( " " ) ); //this one is used in jabber: note, we should escape all &#xx; return data; } TQString Message::escape( const TQString &text ) { TQString html = TQStyleSheet::escape( text ); //Replace carriage returns inside the text html.replace( TQString::fromLatin1( "\n" ), TQString::fromLatin1( "
" ) ); //Replace a tab with 4 spaces html.replace( TQString::fromLatin1( "\t" ), TQString::fromLatin1( "    " ) ); //Replace multiple spaces with   //do not replace every space so we break the linebreak html.replace( TQRegExp( TQString::fromLatin1( "\\s\\s" ) ), TQString::fromLatin1( "  " ) ); return html; } TQString Message::plainBody() const { TQString body=d->body; if( d->format & RichText ) { body = unescape( body ); } return body; } TQString Message::escapedBody() const { TQString escapedBody=d->body; // kdDebug(14000) << k_funcinfo << escapedBody << " " << d->rtfOverride << endl; if( d->format & PlainText ) { escapedBody=escape( escapedBody ); } else if( d->format & RichText && d->rtfOverride) { //remove the rich text escapedBody = escape (unescape( escapedBody ) ); } return escapedBody; } TQString Message::parsedBody() const { //kdDebug(14000) << k_funcinfo << "messageformat: " << d->format << endl; if( d->format == ParsedHTML ) { return d->body; } else { return Kopete::Emoticons::parseEmoticons(parseLinks(escapedBody(), RichText)); } } static TQString makeRegExp( const char *pattern ) { const TQString urlChar = TQString::fromLatin1( "\\+\\-\\w\\./#@&;:=\\?~%_,\\!\\$\\*\\(\\)" ); const TQString boundaryStart = TQString::fromLatin1( "(^|[^%1])(" ).arg( urlChar ); const TQString boundaryEnd = TQString::fromLatin1( ")([^%1]|$)" ).arg( urlChar ); return boundaryStart + TQString::fromLatin1(pattern) + boundaryEnd; } TQString Message::parseLinks( const TQString &message, MessageFormat format ) { if ( format == ParsedHTML ) return message; if ( format & RichText ) { // < in HTML *always* means start-of-tag TQStringList entries = TQStringList::split( TQChar('<'), message, true ); TQStringList::Iterator it = entries.begin(); // first one is different: it doesn't start with an HTML tag. if ( it != entries.end() ) { *it = parseLinks( *it, PlainText ); ++it; } for ( ; it != entries.end(); ++it ) { TQString curr = *it; // > in HTML means start-of-tag if and only if it's the first one after a < int tagclose = curr.find( TQChar('>') ); // no >: the HTML is broken, but we can cope if ( tagclose == -1 ) continue; TQString tag = curr.left( tagclose + 1 ); TQString body = curr.mid( tagclose + 1 ); *it = tag + parseLinks( body, PlainText ); } return entries.join(TQString::fromLatin1("<")); } TQString result = message; // common subpatterns - may not contain matching parens! const TQString name = TQString::fromLatin1( "[\\w\\+\\-=_\\.]+" ); const TQString userAndPassword = TQString::fromLatin1( "(?:%1(?::%1)?\\@)" ).arg( name ); const TQString urlChar = TQString::fromLatin1( "\\+\\-\\w\\./#@&;:=\\?~%_,\\!\\$\\*\\(\\)" ); const TQString urlSection = TQString::fromLatin1( "[%1]+" ).arg( urlChar ); const TQString domain = TQString::fromLatin1( "[\\-\\w_]+(?:\\.[\\-\\w_]+)+" ); //Replace http/https/ftp links: // Replace (stuff)://[user:password@](linkstuff) with a link result.replace( TQRegExp( makeRegExp("\\w+://%1?\\w%2").arg( userAndPassword, urlSection ) ), TQString::fromLatin1("\\1\\2\\3" ) ); // Replace www.X.Y(linkstuff) with a http: link result.replace( TQRegExp( makeRegExp("%1?www\\.%2%3").arg( userAndPassword, domain, urlSection ) ), TQString::fromLatin1("\\1\\2\\3" ) ); //Replace Email Links // Replace user@domain with a mailto: link result.replace( TQRegExp( makeRegExp("%1@%2").arg( name, domain ) ), TQString::fromLatin1("\\1\\2\\3") ); //Workaround for Bug 85061: Highlighted URLs adds a ' ' after the URL itself // the trailing   is included in the url. result.replace( TQRegExp( TQString::fromLatin1("(timeStamp; } const Contact *Message::from() const { return d->from; } TQPtrList Message::to() const { return d->to; } Message::MessageType Message::type() const { return d->type; } TQString Message::requestedPlugin() const { return d->requestedPlugin; } TQColor Message::fg() const { return d->fgColor; } TQColor Message::bg() const { return d->bgColor; } TQFont Message::font() const { //TQDomElement bodyNode = d->xmlDoc.elementsByTagName( TQString::fromLatin1("body") ).item(0).toElement(); //return TQFont( bodyNode.attribute( TQString::fromLatin1("font") ), bodyNode.attribute( TQString::fromLatin1("fontsize") ).toInt() ); return d->font; } TQString Message::subject() const { return d->subject; } Message::MessageFormat Message::format() const { return d->format; } Message::MessageDirection Message::direction() const { return d->direction; } Message::MessageImportance Message::importance() const { return d->importance; } ChatSession *Message::manager() const { return d->manager; } void Message::setManager(ChatSession *kmm) { detach(); d->manager=kmm; } TQString Message::getHtmlStyleAttribute() const { TQString styleAttribute; styleAttribute = TQString::fromUtf8("style=\""); // Affect foreground(color) and background color to message. if( !d->fgOverride && d->fgColor.isValid() ) { styleAttribute += TQString::fromUtf8("color: %1; ").arg(d->fgColor.name()); } if( !d->bgOverride && d->bgColor.isValid() ) { styleAttribute += TQString::fromUtf8("background-color: %1; ").arg(d->bgColor.name()); } // Affect font parameters. if( !d->rtfOverride && d->font!=TQFont() ) { TQString fontstr; if(!d->font.family().isNull()) fontstr+=TQString::fromLatin1("font-family: ")+d->font.family()+TQString::fromLatin1("; "); if(d->font.italic()) fontstr+=TQString::fromLatin1("font-style: italic; "); if(d->font.strikeOut()) fontstr+=TQString::fromLatin1("text-decoration: line-through; "); if(d->font.underline()) fontstr+=TQString::fromLatin1("text-decoration: underline; "); if(d->font.bold()) fontstr+=TQString::fromLatin1("font-weight: bold;"); styleAttribute += fontstr; } styleAttribute += TQString::fromUtf8("\""); return styleAttribute; } // KDE4: Move that to a utils class/namespace TQString Message::decodeString( const TQCString &message, const TQTextCodec *providedCodec, bool *success ) { /* Note to everyone. This function is not the most efficient, that is for sure. However, it *is* the only way we can be guarenteed that a given string is decoded properly. */ if( success ) *success = true; // Avoid heavy codec tests on empty message. if( message.isEmpty() ) return TQString::fromAscii( message ); //Check first 128 chars int charsToCheck = message.length(); charsToCheck = 128 > charsToCheck ? charsToCheck : 128; //They are providing a possible codec. Check if it is valid if( providedCodec && providedCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) { //All chars decodable. return providedCodec->toUnicode( message ); } //Check if it is UTF if( KStringHandler::isUtf8(message) ) { //We have a UTF string almost for sure. At least we know it will be decoded. return TQString::fromUtf8( message ); } //Try codecForContent - exact match TQTextCodec *testCodec = TQTextCodec::codecForContent(message, charsToCheck); if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) { //All chars decodable. return testCodec->toUnicode( message ); } kdWarning(14000) << k_funcinfo << "Unable to decode string using provided codec(s), taking best guesses!" << endl; if( success ) *success = false; //We don't have any clues here. //Try local codec testCodec = TQTextCodec::codecForLocale(); if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) { //All chars decodable. kdDebug(14000) << k_funcinfo << "Using locale's codec" << endl; return testCodec->toUnicode( message ); } //Try latin1 codec testCodec = TQTextCodec::codecForMib(4); if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= 0 ) { //All chars decodable. kdDebug(14000) << k_funcinfo << "Using latin1" << endl; return testCodec->toUnicode( message ); } kdDebug(14000) << k_funcinfo << "Using latin1 and cleaning string" << endl; //No codec decoded. Just decode latin1, and clean out any junk. TQString result = TQString::fromLatin1( message ); const uint length = message.length(); for( uint i = 0; i < length; ++i ) { if( !result[i].isPrint() ) result[i] = '?'; } return result; }