Files
kmess/src/chat/chatmessagestyle.cpp
T
2009-07-17 15:44:01 +00:00

1094 lines
29 KiB
C++

/***************************************************************************
chatmessagestyle.cpp - description
-------------------
begin : Sat Okt 29 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 "chatmessagestyle.h"
#include "../contact/contactbase.h"
#include "../utils/richtextparser.h"
#include "../utils/kmessshared.h"
#include "../currentaccount.h"
#include "../emoticonmanager.h"
#include "../kmessdebug.h"
#include "xsltransformation.h"
#include <QDateTime>
#include <QFile>
#include <QList>
#include <QRegExp>
#include <QTextDocument>
#include <KLocale>
#include <KStandardDirs>
#include <KUrl>
// Originally from libxml/chvalid.h, but with the last check in xmlIsCharQ removed;
// the last check was for four-byte characters, we only check shorts. Removing
// the last check fixes two warnings on the console. :)
#define xmlIsChar_ch(c) (((0x9 <= (c)) && ((c) <= 0xa)) || \
((c) == 0xd) || \
(0x20 <= (c)))
#define xmlIsCharQ(c) (((c) < 0x100) ? \
xmlIsChar_ch((c)) :\
(((0x100 <= (c)) && ((c) <= 0xd7ff)) || \
((0xe000 <= (c)) && ((c) <= 0xfffd))))
// The constructor
ChatMessageStyle::ChatMessageStyle()
: QObject(0)
, canConvert_(false)
, contactFontColor_("#000000")
, groupFollowupMessages_(false)
, showTime_(true)
, useContactFont_(false)
, useEmoticons_(true)
, useFontEffects_(true)
, useFormatting_(false)
, allowEmoticonLinks_(true)
{
xslTransformation_ = new XslTransformation();
}
// The destructor
ChatMessageStyle::~ChatMessageStyle()
{
delete xslTransformation_;
}
// Return whether XSL conversion works with the current style
bool ChatMessageStyle::canConvert() const
{
return canConvert_;
}
// Convert a chat message to HTML string
QString ChatMessageStyle::convertMessage(const ChatMessage &message)
{
// Reset state, changed by parseMsnString
pendingEmoticonTags_.clear();
// Create and convert the XML message
if(xslTransformation_ == 0 || ! xslTransformation_->hasStylesheet())
{
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "no XSL theme loaded (theme=" << name_ << "),"
<< " using fallback theme." << endl;
#endif
// Create fallback message.
return createFallbackMessage(message);
}
else
{
QString xmlMessage ( convertMessageToXml( message ) );
QString parsedMessage( xslTransformation_->convertXmlString(xmlMessage) );
// Strip DOCTYPE because it should only appear in convertMessageRoot() output
parsedMessage = stripDoctype(parsedMessage);
if( parsedMessage.isNull() )
{
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "XSL conversion failed (theme=" << name_ << "),"
<< " using fallback theme." << endl;
#endif
return createFallbackMessage(message);
}
else if( isEmptyResult(parsedMessage) )
{
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "XSL conversion returned no data (theme=" << name_ << "),"
<< " using fallback theme." << endl;
#endif
return createFallbackMessage(message);
}
else
{
// Received expected result
return parsedMessage; // DONE!
}
}
}
// Convert a group of chat message to HTML string
QString ChatMessageStyle::convertMessageList(const QList<ChatMessage*> &messageList)
{
// Reset state, changed by parseMsnString
pendingEmoticonTags_.clear();
// Warn for empty list, avoid broken markup
if( messageList.isEmpty() )
{
kWarning() << "no messages given!";
return QString::null;
}
else if( messageList.count() == 1 )
{
// When there is only one message, don't wrap it in a <messagegroup>.
// Makes the calling-code easier, and the layout consistent.
return convertMessage( *messageList.first() );
}
// Create iterator
QListIterator<ChatMessage*> it( messageList );
bool hasStyle = (xslTransformation_ != 0 && xslTransformation_->hasStylesheet());
QString parsedMessage;
// Avoid all the trouble when we can't create
if( ! hasStyle )
{
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "no XSL theme loaded (theme=" << name_ << "),"
<< " using fallback theme." << endl;
#endif
}
else
{
// Create XML string.
QDateTime lastMessageDate;
QString xmlMessage( "<messagegroup>\n" );
while( it.hasNext() )
{
const ChatMessage &message = *it.next();
// Do not group messages which are distant in time by more than 10 minutes
if( lastMessageDate.isValid()
&& lastMessageDate.secsTo( message.getDateTime() ) > 600 )
{
// Flush the XML to the parser, then start again for the next messages
xmlMessage += "</messagegroup>\n";
parsedMessage += xslTransformation_->convertXmlString( xmlMessage );
xmlMessage = "<messagegroup>\n";
}
xmlMessage += convertMessageToXml( message ) + "\n";
lastMessageDate = message.getDateTime();
}
xmlMessage += "</messagegroup>\n";
// Convert the current messages
parsedMessage += xslTransformation_->convertXmlString(xmlMessage);
// Strip DOCTYPE because it should only appear in convertMessageRoot() output
parsedMessage = stripDoctype(parsedMessage);
if( parsedMessage.isNull() )
{
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "XSL conversion failed (theme=" << name_ << "),"
<< " using fallback theme." << endl;
#endif
}
else if( isEmptyResult(parsedMessage) )
{
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "XSL conversion returned no data (theme=" << name_ << "),"
<< " using fallback theme." << endl;
#endif
}
else
{
// Received expected result
return parsedMessage; // DONE!
}
}
// Conversion failed, create fallback
while( it.hasNext() )
{
parsedMessage += createFallbackMessage( *it.next() );
}
return parsedMessage;
}
// Convert a string with XML to HTML
QString ChatMessageStyle::convertXmlMessageList( const QString &xmlMessagesList )
{
QString parsedMessage;
if( xslTransformation_ != 0 && xslTransformation_->hasStylesheet() )
{
// Convert
parsedMessage = xslTransformation_->convertXmlString( xmlMessagesList );
// Strip DOCTYPE because it should only appear in convertMessageRoot() output
parsedMessage = stripDoctype( parsedMessage );
if( parsedMessage.isNull() )
{
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "XSL conversion failed (theme=" << name_ << "), cannot convert.";
#endif
}
else if( isEmptyResult( parsedMessage ) )
{
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "XSL conversion returned no data (theme=" << name_ << "), cannot convert.";
#endif
}
else
{
// Received expected result
return parsedMessage;
}
}
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "no XSL theme loaded (theme=" << name_ << "), cannot convert.";
#endif
// Conversion failed, return the original string.
// TODO: Parse the XML (using QDom classes or regexps) and create
// ChatMessages to send to createFallbackMessage().
return parsedMessage;
}
// Convert the message root.
QString ChatMessageStyle::convertMessageRoot()
{
QString parsedMessage;
// Avoid all the trouble when we can't create a style
if( ! hasStyle() )
{
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "no XSL theme loaded.";
#endif
}
else
{
// Not much to add here yet..
QString xmlMessage( "<messageRoot>"
"</messageRoot>" );
// Convert
parsedMessage = xslTransformation_->convertXmlString(xmlMessage);
if( ! parsedMessage.isNull() )
{
if( isEmptyResult(parsedMessage) )
{
// Indicate the chat style doesn't define the whole header/footer,
// so KMess can use it's default instead.
return QString::null;
}
return parsedMessage; // DONE!
}
else
{
#ifdef KMESSDEBUG_CHATVIEW
kWarning() << "XSL conversion failed (theme=" << name_ << ").";
#endif
}
}
return QString::null;
}
// Convert the message as HTML, fallback method when XML fails.
QString ChatMessageStyle::createFallbackMessage(const ChatMessage &message)
{
QString color;
int type = message.getType();
QString handle ( message.getContactHandle() );
QString name ( message.getContactName() );
QString body ( message.getBody() );
QString fontDir ( body.isRightToLeft() ? "rtl" : "ltr" );
// Reset state, changed by parseMsnString
pendingEmoticonTags_.clear();
// Create the fallback message when the XML/XSL conversion failed
if( message.isNormalMessage() )
{
// Contact message
QString fontBefore;
QString fontAfter;
QString time;
QFont font;
QString color;
// Extract fonts
parseFont(message, font, color, fontBefore, fontAfter);
// Escape HTML, replace standard emoticons, links and formatting
RichTextParser::parseMsnString( name, useEmoticons_, true, false, useFormatting_, allowEmoticonLinks_ );
// Replace font special effects, like *bold*
if( useFontEffects_ )
{
parseEffects( name );
}
// Body can already be HTML if generated internally.
if( ! message.hasHtmlBody() )
{
// Escape HTML, replace all emoticons, links and formatting
RichTextParser::parseMsnString( body, useEmoticons_, false, true, useFormatting_, allowEmoticonLinks_, handle, pendingEmoticonTags_ );
// Replace font special effects, like *bold*
if( useFontEffects_ )
{
parseEffects( body );
}
}
// Replace newlines with <br/>
parseBody(body);
// Misc variables
if( showTime_ )
{
QDateTime messageTime = message.getDateTime();
if ( showDate_ )
{
time = KGlobal::locale()->formatDateTime( messageTime, KLocale::ShortDate, showSeconds_ );
}
else
{
time = KGlobal::locale()->formatTime( messageTime.time(), showSeconds_ );
}
}
color = (type == ChatMessage::TYPE_INCOMING || type == ChatMessage::TYPE_OFFLINE_INCOMING)
? "666699" : "666666";
// Create HTML
return "<div dir='" + fontDir + "'><font color='" + color + "'>" + (showTime_ ? time + " " : "")
+ i18n("%1 says:", name)
+ "<br></font>"
+ fontBefore + body + "<br><br>" + fontAfter
+ "</div>";
}
else if( type == ChatMessage::TYPE_SYSTEM )
{
// Red system message
parseBody( KMessShared::htmlEscape( body ) );
return "<div dir='" + fontDir + "'><hr size='2' color='red'>"
"<font color='red'>" + body + "</font>"
"<hr size='2' color='red'></div>";
}
else if( type == ChatMessage::TYPE_APPLICATION
|| type == ChatMessage::TYPE_APPLICATION_FILE )
{
// Blue application message
parseBody( KMessShared::htmlEscape( body ) );
return "<div dir='" + fontDir + "'><font color='blue'>" + body + "</font><br></div>";
}
else if( type == ChatMessage::TYPE_PRESENCE )
{
// Gray presence message
parseBody( KMessShared::htmlEscape( body ) );
return "<div dir='" + fontDir + "'><font color='gray'>" + body + "</font><br></div>";
}
else
{
// Purple notification message
parseBody( KMessShared::htmlEscape( body ) );
return "<div dir='" + fontDir + "'><font color='purple'>" + body + "</font><br></div>";
}
}
// Convert the message as XML.
QString ChatMessageStyle::convertMessageToXml( const ChatMessage &message, bool isHistory )
{
uint timestamp = 0;
QString fontBefore;
QString fontAfter;
QString xmlMessage;
QString parsedMessage;
QString typeString;
QString color;
QFont font;
// Remove any characters which are not allowed in XML
const ushort* utf16 = message.getBody().utf16();
ushort* newutf16 = new ushort[ message.getBody().size() ];
int j = 0;
for( int i = 0; i < message.getBody().size(); ++i )
{
// Utf16 sometimes uses more than two bytes for a character, but in those cases,
// both parts will be valid, so xmlIsCharQ (checking if a short is valid) will always return true.
if( xmlIsCharQ( utf16[i] ) )
{
newutf16[ j++ ] = utf16[i];
}
#ifdef KMESSDEBUG_CHATVIEW
else
{
kDebug() << "Found invalid character with code " << (int)utf16[i] << ", not showing";
}
#endif
}
QString body = QString::fromUtf16( newutf16, j );
// Get message info
int type = message.getType();
QString handle ( message.getContactHandle() );
QString name ( message.getContactName() );
bool isRtl = body.isRightToLeft();
bool nameIsRtl = name.isRightToLeft();
// See if this is a contact's message and not the user
// Prepare the strings for HTML
// App messages are not parsed, they may contain HTML (links to click on, etc..)
// Only exception to this is presence messages to maintain consistency.
bool isPresence = message.getType() == ChatMessage::TYPE_PRESENCE;
if( message.isNormalMessage() || isPresence )
{
// Extract fonts
parseFont(message, font, color, fontBefore, fontAfter);
// Escape HTML, replace standard emoticons, links and formatting
RichTextParser::parseMsnString( name, useEmoticons_, true, false, useFormatting_, allowEmoticonLinks_ );
// Parse effects characters (_, *, /)
if( useFontEffects_ )
{
parseEffects( name );
}
// Body can already be HTML if generated internally.
if( ! message.hasHtmlBody() )
{
// Escape HTML, replace all emoticons, links and formatting
// if this is a presence message, show emoticons only. Otherwise, show the works depending on configuration.
RichTextParser::parseMsnString( body, useEmoticons_, false,
!isPresence,
(isPresence ? false : useFormatting_),
(isPresence ? false : allowEmoticonLinks_),
(isPresence ? QString() : handle), pendingEmoticonTags_ );
if( useFontEffects_ )
{
parseEffects( body );
}
}
}
// Replace newlines with <br/> tags
parseBody(body);
// Show the message time
// TODO: maybe timestamps should always available in the histories, just not necessarily shown?
if( showTime_ )
{
// Get the time
const QDateTime &messageDate = message.getDateTime();
timestamp = messageDate.toTime_t();
}
// Get message type for XSL
switch(type)
{
case ChatMessage::TYPE_INCOMING: typeString = "incoming"; break;
case ChatMessage::TYPE_OUTGOING: typeString = "outgoing"; break;
case ChatMessage::TYPE_SYSTEM: typeString = "system"; break;
case ChatMessage::TYPE_APPLICATION_FILE:
case ChatMessage::TYPE_APPLICATION: typeString = "application"; break;
case ChatMessage::TYPE_OFFLINE_INCOMING: typeString = "offlineIncoming"; break;
case ChatMessage::TYPE_PRESENCE: typeString = "presence"; break;
case ChatMessage::TYPE_NOTIFICATION:
default:
typeString = "notification";
break;
}
// IMPORTANT:
// The QStyleSheet::escape() function doesn't escape quotes.
// Special care needs to be taken to avoid XML parsing errors.
// Create an XML message for the XSL theme conversion
// "<?xml version='1.0'?>"
if ( isHistory )
{
xmlMessage = "<message"
" type='" + KMessShared::htmlEscape( typeString ) + "'"
+ " timestamp='" + QString::number( timestamp ) + "'" +
">\n";
}
else
{
QString timeString;
const QDateTime &datetime = QDateTime::fromTime_t( timestamp );
if ( showDate_ )
{
timeString = KGlobal::locale()->formatDateTime( datetime, KLocale::ShortDate, showSeconds_ );
}
else
{
timeString = KGlobal::locale()->formatTime( datetime.time(), showSeconds_ );
}
xmlMessage = "<message"
" type='" + KMessShared::htmlEscape( typeString ) + "'" +
( ! showTime_ ? QString() : " time='" + timeString + "'" ) +
">\n";
}
// The contact can be empty for some notification messges
if(! message.getContactHandle().isEmpty() )
{
xmlMessage += " <from>\n"
" <contact contactId='" + KMessShared::htmlEscape( message.getContactHandle() ) + "'>\n";
// If the user doesn't have a display picture, the style will use a default image.
if( ! message.getContactPicturePath().isEmpty() && QFile::exists( message.getContactPicturePath() ) )
{
xmlMessage += " <displayPicture url='" + KMessShared::htmlEscape( message.getContactPicturePath() ) + "' />\n";
}
if( ! name.isEmpty() )
{
xmlMessage += " <displayName text='" + KMessShared::htmlEscape( name ) + "' dir='" + (nameIsRtl ? "rtl" : "ltr") + "' />\n";
}
xmlMessage += " </contact>\n"
" </from>\n";
}
// Add message
xmlMessage += " <body"
" color='" + KMessShared::htmlEscape( color ) + "'"
" fontFamily='" + KMessShared::htmlEscape( font.family() ) + "'"
" fontSize='" + QString::number( font.pointSize() ) + "'"
" fontBold='" + (font.bold() ? "1" : "0") + "'"
" fontItalic='" + (font.italic() ? "1" : "0") + "'"
" fontUnderline='" + (font.underline() ? "1" : "0") + "'"
" fontBefore='" + KMessShared::htmlEscape( fontBefore ) + "'"
" fontAfter='" + KMessShared::htmlEscape( fontAfter ) + "'"
" dir='" + (isRtl ? "rtl" : "ltr") + "'"
">\n" +
KMessShared::htmlEscape( body ) + '\n' +
"</body>\n" // escape again for XML
"</message>\n";
return xmlMessage;
}
// Return the base folder of the style.
const QString & ChatMessageStyle::getBaseFolder() const
{
return baseFolder_;
}
// Return the font used for contact messages, if forced to.
const QFont& ChatMessageStyle::getContactFont() const
{
return contactFont_;
}
// Return the color of the forced contact font.
const QString& ChatMessageStyle::getContactFontColor() const
{
return contactFontColor_;
}
// Return the xsl stylesheet. Return null if there is none.
QString ChatMessageStyle::getStyleSheet() const
{
QString xslFile( baseFolder_ + name_ + ".xsl" );
if( ! hasStyle() || ! QFile::exists( xslFile ) )
{
return QString();
}
return xslFile;
}
// Return the css file attached to the stylesheet. Return null if there is none.
QString ChatMessageStyle::getCssFile() const
{
QString cssFile( baseFolder_ + name_ + ".css" );
if( ! QFile::exists(cssFile) )
{
return QString::null;
}
else
{
return cssFile;
}
}
// Return the currently used emoticon style
const QString &ChatMessageStyle::getEmoticonStyle() const
{
return emoticonStyle_;
}
// Return the grow follow up messages
bool ChatMessageStyle::getGroupFollowupMessages() const
{
return groupFollowupMessages_;
}
// Return the name of the style.
const QString & ChatMessageStyle::getName() const
{
return name_;
}
// Return whether or not emoticon links are allowed
bool ChatMessageStyle::getAllowEmoticonLinks() const
{
return allowEmoticonLinks_;
}
// Return if the date is displayed in messages
bool ChatMessageStyle::getShowMessageDate() const
{
return showDate_;
}
// Return if the seconds are displayed in the message times
bool ChatMessageStyle::getShowMessageSeconds() const
{
return showSeconds_;
}
// Return if the time is displayed in messages
bool ChatMessageStyle::getShowMessageTime() const
{
return showTime_;
}
// Return the ID's of inserted <img> tags for the pending emoticons.
const QStringList & ChatMessageStyle::getPendingEmoticonTagIds() const
{
return pendingEmoticonTags_;
}
// Return whether or not to show text formatting in chat messages
bool ChatMessageStyle::getUseChatFormatting() const
{
return useFormatting_;
}
// Return whether or not to show contact messages in the stored font.
bool ChatMessageStyle::getUseContactFont() const
{
return useContactFont_;
}
// Return whether or not the emoticons are visible
bool ChatMessageStyle::getUseEmoticons() const
{
return useEmoticons_;
}
// Return whether or not the font effects are visible
bool ChatMessageStyle::getUseFontEffects() const
{
return useFontEffects_;
}
// Return whether or not the formatting is visible
bool ChatMessageStyle::getUseFormatting() const
{
return useFormatting_;
}
// Return whether the given result is empty
bool ChatMessageStyle::isEmptyResult( const QString &parsedMessage )
{
return stripDoctype(parsedMessage).trimmed().isEmpty();
}
// Return whether an style is loaded.
bool ChatMessageStyle::hasStyle() const
{
return (xslTransformation_ != 0 && xslTransformation_->hasStylesheet());
}
// Replace the newline characters
void ChatMessageStyle::parseBody(QString &body) const
{
// Replace any newline characters in the message with "<br>" so that carriage returns will show properly.
body = body.replace(QRegExp("\r?\n?$"), QString::null) // Remove last \n
.replace( "\r\n", "<br/>" )
.replace( '\r', "<br/>" )
.replace( '\n', "<br/>" );
}
// Do some effects characters (ie, bold, underline and italic specials)
void ChatMessageStyle::parseEffects(QString &text) const
{
int offset = 0;
int nextOffset = 0;
QRegExp effectsSearch;
QString effectsCharacter;
QChar boundaryCharacter;
int tagPosOpen, tagPosClose;
QString replacement;
// Process bold, italics, underline
const char* effectHtml = "biu"; // bold, italic, underline
QString effectCharacters( "*/_" );
for( int i = 0; i < 3; i++ )
{
effectsCharacter = QRegExp::escape( effectCharacters.mid(i, 1) );
effectsSearch.setPattern( effectsCharacter + "([\\d\\w\\s]+)" + effectsCharacter );
offset = 0;
while( offset >= 0 )
{
// Find next pattern match
offset = effectsSearch.indexIn( text, nextOffset );
if( offset == -1 )
{
break;
}
// Skip effects contained in HTML tags
tagPosOpen = text.indexOf( "<", nextOffset );
tagPosClose = text.indexOf( ">", tagPosOpen );
if( tagPosOpen != -1 && tagPosClose != -1 && offset > tagPosOpen && offset < tagPosClose )
{
// Continue from the first character after the HTML tag
nextOffset = tagPosClose + 1;
continue;
}
nextOffset = offset + effectsSearch.matchedLength();
// Abort if there is a normal char before
if( offset != 0 )
{
boundaryCharacter = text.at( offset - 1 );
if( ! boundaryCharacter.isSpace() && ! boundaryCharacter.isPunct() )
{
continue;
}
}
// Abort if there is a normal char after
if( ( offset + effectsSearch.matchedLength() ) < text.length() )
{
boundaryCharacter = text.at( offset + effectsSearch.matchedLength() );
if( ! boundaryCharacter.isSpace() && ! boundaryCharacter.isPunct() )
{
continue;
}
}
// Replace if start and end are free.
if( effectHtml[i] == 'b' )
{
// Bold is standard
replacement = "*<b>" + effectsSearch.cap(1) + "</b>*";
}
else if( effectHtml[i] == 'i' )
{
// Same for italics
replacement = "/<i>" + effectsSearch.cap(1) + "</i>/";
}
else if( effectHtml[i] == 'u' )
{
// Same for underline
replacement = "<u>&nbsp;" + effectsSearch.cap(1) + "&nbsp;</u>";
}
// Replace and change offset
text.replace(offset, effectsSearch.matchedLength(), replacement);
nextOffset = offset + replacement.length() + 2;
}
}
}
// Parse the font tags
void ChatMessageStyle::parseFont(const ChatMessage &message, QFont &font, QString &color, QString& fontBefore, QString& fontAfter) const
{
// Extract the font from the message.
ChatMessage::MessageType type = message.getType();
if( useContactFont_ && (type == ChatMessage::TYPE_INCOMING || type == ChatMessage::TYPE_OFFLINE_INCOMING) )
{
// Replace the given font with the user's stored contact font
font = contactFont_;
color = contactFontColor_;
}
else
{
// Use the font settings from the message
font = message.getFont();
color = message.getFontColor();
}
// Include the dir="rtl" tag to the font so the text is at least displayed in the right direction.
// To make it align right as well, the dir needs to be assigned to a block element.
// This can't be done here as it would influence the chat style.
QString fontDir( message.getBody().isRightToLeft() ? "rtl" : "ltr" );
// Set the font size, be it the user's or the contacts'
QString size( " style=\"font-size:" + QString::number( font.pointSize() ) + "pt\"" );
// Create the font HTML for the message
fontBefore.clear();
fontAfter.clear();
if( font.bold() ) fontBefore += "<b>";
if( font.italic() ) fontBefore += "<i>";
if( font.underline() ) fontBefore += "<u>";
fontBefore += "<font face=\"" + font.family() + "\" color=\"" + color + "\" dir=\"" + fontDir + "\"" + size + ">";
fontAfter += "</font>";
if( font.underline() ) fontAfter += "</u>";
if( font.italic() ) fontAfter += "</i>";
if( font.bold() ) fontAfter += "</b>";
}
// The the contact font
void ChatMessageStyle::setContactFont(const QFont &font)
{
contactFont_ = font;
}
// The the contact font color
void ChatMessageStyle::setContactFontColor(const QString &fontColor)
{
contactFontColor_ = fontColor;
}
// Set the emoticon style
void ChatMessageStyle::setEmoticonStyle( const QString &style )
{
emoticonStyle_ = style;
}
// Set grow follow up messages
void ChatMessageStyle::setGroupFollowupMessages( bool groupFollowupMessages )
{
groupFollowupMessages_ = groupFollowupMessages;
}
// Set whether or not emoticon links are allowed
void ChatMessageStyle::setAllowEmoticonLinks( bool allowEmoticonLinks )
{
allowEmoticonLinks_ = allowEmoticonLinks;
}
// Set the show time state
void ChatMessageStyle::setShowTime(bool showTime)
{
showTime_ = showTime;
}
// Set the show date state
void ChatMessageStyle::setShowDate(bool showDate)
{
showDate_ = showDate;
}
// Set the show seconds state
void ChatMessageStyle::setShowSeconds(bool showSeconds)
{
showSeconds_ = showSeconds;
}
// Set the message style, return false if it failed
bool ChatMessageStyle::setStyle(const QString &style)
{
// Avoid reloading the same style again
if( style == name_ )
{
return hasStyle();
}
KStandardDirs *dirs = KGlobal::dirs();
QString path( dirs->findResource( "data", "kmess/styles/" + style + "/" + style + ".xsl" ) );
if(path.isNull())
{
kWarning() << "could not find the style named '" << style << "'.";
return false;
}
// Set stylesheet
name_ = style;
xslTransformation_->setStylesheet(path);
bool styleSheetLoaded = xslTransformation_->hasStylesheet();
if(styleSheetLoaded)
{
// Update the base folder
KUrl pathUrl;
pathUrl.setPath(path);
baseFolder_ = pathUrl.directory( KUrl::AppendTrailingSlash );
QMap<QString,QString> parameters;
parameters["basepath"] = baseFolder_;
parameters["csspath"] = getCssFile();
xslTransformation_->setParameters(parameters);
#ifdef KMESSDEBUG_CHATVIEW
kDebug() << "Style" << style << "loaded from path:" << path << ". Base folder:" << baseFolder_;
#endif
}
#ifdef KMESSDEBUG_CHATVIEW
else
{
kDebug() << "Unable to load style:" << style << "from path:" << path;
}
#endif
// Also verify if the XSL conversion works with the new style
canConvert_ = ( ! xslTransformation_->convertXmlString( "<messageRoot></messageRoot>" ).isEmpty() );
return styleSheetLoaded;
}
// Enable or disable contact font overrides
void ChatMessageStyle::setUseContactFont(bool useContactFont)
{
useContactFont_ = useContactFont;
}
// Enable or disable emoticons
void ChatMessageStyle::setUseEmoticons(bool useEmoticons)
{
useEmoticons_ = useEmoticons;
}
// Enable or disable font effects
void ChatMessageStyle::setUseFontEffects(bool useFontEffects)
{
useFontEffects_ = useFontEffects;
}
// Enable or disable MSN Plus formatting
void ChatMessageStyle::setUseFormatting( bool useFormatting )
{
useFormatting_ = useFormatting;
}
// Strip the DOCTYPE tag from the message
QString ChatMessageStyle::stripDoctype( const QString &parsedMessage )
{
if( parsedMessage.startsWith( "<!DOCTYPE" ) )
{
QRegExp re(">\r?\n?");
int endPos = parsedMessage.indexOf( re );
if( endPos == -1 )
{
kWarning() << "Could not strip DOCTYPE tag: end position not found!";
return parsedMessage;
}
// Strip both end end possible \r\n character
return parsedMessage.mid( endPos + re.matchedLength() );
}
else
{
return parsedMessage;
}
}
#include "chatmessagestyle.moc"