1094 lines
29 KiB
C++
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> " + effectsSearch.cap(1) + " </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"
|
|
|