make webinterface translatable. there are around 20 short strings, all with context...
[kdenetwork.git] / kopete / libkopete / kopetemessage.cpp
blob5e67b23b5c8450934e640d9cb8fa2b3d0a4391af
1 /*
2 kopetemessage.cpp - Base class for Kopete messages
4 Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org>
5 Copyright (c) 2002-2006 by Olivier Goffart <ogoffart@kde.org>
6 Copyright (c) 2006-2007 by Charles Connell <charles@connells.org>
7 Copyright (c) 2007 by Michaƫl Larouche <larouche@kde.org>
9 Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org>
11 *************************************************************************
12 * *
13 * This library is free software; you can redistribute it and/or *
14 * modify it under the terms of the GNU Lesser General Public *
15 * License as published by the Free Software Foundation; either *
16 * version 2 of the License, or (at your option) any later version. *
17 * *
18 *************************************************************************
21 #include <stdlib.h>
23 #include <QtCore/QDateTime>
24 #include <QtCore/QLatin1String>
25 #include <QtCore/QPointer>
26 #include <QtCore/QRegExp>
27 #include <QtCore/QTextCodec>
28 #include <QtGui/QTextDocument>
29 #include <QtGui/QColor>
31 #include <kdebug.h>
32 #include <kstringhandler.h>
33 #include <kemoticons.h>
35 #include "kopetemessage.h"
36 #include "kopetemetacontact.h"
37 #include "kopeteprotocol.h"
38 #include "kopetechatsession.h"
39 #include "kopetecontact.h"
40 #include "kopeteemoticons.h"
43 namespace Kopete
46 class Message::Private
47 : public QSharedData
49 public:
50 Private()
51 : direction(Internal), format(Qt::PlainText), type(TypeNormal), importance(Normal), backgroundOverride(false),
52 foregroundOverride(false), richTextOverride(false), isRightToLeft(false), timeStamp( QDateTime::currentDateTime() ),
53 body(new QTextDocument), escapedBodyDirty(true)
55 Private (const Private &other);
56 ~Private();
58 QPointer<Contact> from;
59 ContactPtrList to;
60 QPointer<ChatSession> manager;
62 MessageDirection direction;
63 Qt::TextFormat format;
64 MessageType type;
65 QString requestedPlugin;
66 MessageImportance importance;
67 bool backgroundOverride;
68 bool foregroundOverride;
69 bool richTextOverride;
70 bool isRightToLeft;
71 QDateTime timeStamp;
72 QFont font;
73 QStringList classes;
75 QColor foregroundColor;
76 QColor backgroundColor;
77 QString subject;
79 QTextDocument* body;
80 mutable QString escapedBody;
81 mutable bool escapedBodyDirty;
84 Message::Private::Private (const Message::Private &other)
85 : QSharedData (other)
87 from = other.from;
88 to = other.to;
89 manager = other.manager;
91 direction = other.direction;
92 format = other.format;
93 type = other.type;
94 requestedPlugin = other.requestedPlugin;
95 importance = other.importance;
96 backgroundOverride = other.backgroundOverride;
97 foregroundOverride = other.foregroundOverride;
98 richTextOverride = other.richTextOverride;
99 isRightToLeft = other.isRightToLeft;
100 timeStamp = other.timeStamp;
101 font = other.font;
102 classes = other.classes;
104 foregroundColor = other.foregroundColor;
105 backgroundColor = other.backgroundColor;
106 subject = other.subject;
108 body = other.body->clone();
109 escapedBody = other.escapedBody;
110 escapedBodyDirty = other.escapedBodyDirty;
113 Message::Private::~Private ()
115 delete body;
118 Message::Message()
119 : d( new Private )
123 Message::Message( const Contact *fromKC, const Contact *toKC )
124 : d(new Private)
126 d->from = const_cast<Contact*>(fromKC);
127 QList<Contact *> contacts;
128 contacts << const_cast<Contact*>(toKC);
130 d->to = contacts;
133 Message::Message( const Contact *fromKC, const QList<Contact*> &toKC )
134 : d( new Private )
136 d->from = const_cast<Contact*>(fromKC);
137 d->to = toKC;
140 Message::Message( const Message &other )
141 : d(other.d)
145 Message& Message::operator=( const Message &other )
147 d = other.d;
148 return *this;
151 Message::~Message()
155 void Message::setBackgroundOverride( bool enabled )
157 d->backgroundOverride = enabled;
160 void Message::setForegroundOverride( bool enabled )
162 d->foregroundOverride = enabled;
165 void Message::setRichTextOverride( bool enabled )
167 d->richTextOverride = enabled;
170 void Message::setForegroundColor( const QColor &color )
172 d->foregroundColor=color;
175 void Message::setBackgroundColor( const QColor &color )
177 d->backgroundColor=color;
180 void Message::setFont( const QFont &font )
182 d->font = font;
185 void Message::setPlainBody (const QString &body)
187 doSetBody (body, Qt::PlainText);
190 void Message::setHtmlBody (const QString &body)
192 doSetBody (body, Qt::RichText);
195 void Message::doSetBody (const QString &body, Qt::TextFormat f)
197 if (f == Qt::PlainText)
198 d->body->setPlainText(body);
199 else
200 d->body->setHtml(body);
201 d->format = f;
202 d->isRightToLeft = d->body->toPlainText().isRightToLeft();
203 d->escapedBodyDirty = true;
206 void Message::setBody (const QTextDocument *_body)
208 doSetBody (_body, Qt::RichText);
211 void Message::doSetBody (const QTextDocument *body, Qt::TextFormat f)
213 delete d->body;
214 d->body = body->clone(); // delete the old body and replace it with a *copy* of the new one
215 d->format = f;
216 d->isRightToLeft = d->body->toPlainText().isRightToLeft();
217 d->escapedBodyDirty = true;
220 void Message::setImportance(Message::MessageImportance i)
222 d->importance = i;
225 QString Message::unescape( const QString &xml )
227 QString data = xml;
229 // Remove linebreak and multiple spaces. First return nbsp's to normal spaces :)
230 data = data.simplified();
232 int pos;
233 while ( ( pos = data.indexOf( '<' ) ) != -1 )
235 int endPos = data.indexOf( '>', pos + 1 );
236 if( endPos == -1 )
237 break; // No more complete elements left
239 // Take the part between < and >, and extract the element name from that
240 int matchWidth = endPos - pos + 1;
241 QString match = data.mid( pos + 1, matchWidth - 2 ).simplified();
242 int elemEndPos = match.indexOf( ' ' );
243 QString elem = ( elemEndPos == -1 ? match.toLower() : match.left( elemEndPos ).toLower() );
244 if ( elem == QLatin1String( "img" ) )
246 // Replace smileys with their original text'
247 const QString attrTitle = QLatin1String( "title=\"" );
248 int titlePos = match.indexOf( attrTitle, elemEndPos );
249 int titleEndPos = match.indexOf( '"', titlePos + attrTitle.length() );
250 if( titlePos == -1 || titleEndPos == -1 )
252 // Not a smiley but a normal <img>
253 // Don't update pos, we restart at this position :)
254 data.remove( pos, matchWidth );
256 else
258 QString orig = match.mid( titlePos + attrTitle.length(),
259 titleEndPos - titlePos - attrTitle.length() );
260 data.replace( pos, matchWidth, orig );
261 pos += orig.length();
264 else if ( elem == QLatin1String( "/p" ) || elem == QLatin1String( "/div" ) ||
265 elem == QLatin1String( "br" ) )
267 // Replace paragraph, div and line breaks with a newline
268 data.replace( pos, matchWidth, '\n' );
269 pos++;
271 else
273 // Remove all other elements entirely
274 // Don't update pos, we restart at this position :)
275 data.remove( pos, matchWidth );
279 // Replace stuff starting with '&'
280 data.replace( QLatin1String( "&gt;" ), QLatin1String( ">" ) );
281 data.replace( QLatin1String( "&lt;" ), QLatin1String( "<" ) );
282 data.replace( QLatin1String( "&quot;" ), QLatin1String( "\"" ) );
283 data.replace( QLatin1String( "&nbsp;" ), QLatin1String( " " ) );
284 data.replace( QLatin1String( "&amp;" ), QLatin1String( "&" ) );
285 data.replace( QLatin1String( "&#160;" ), QLatin1String( " " ) ); //this one is used in jabber: note, we should escape all &#xx;
287 return data;
290 QString Message::escape( const QString &text )
292 QString html = Qt::escape( text );
293 //Replace carriage returns inside the text
294 html.replace( QLatin1String( "\n" ), QLatin1String( "<br />" ) );
295 //Replace a tab with 4 spaces
296 html.replace( QLatin1String( "\t" ), QLatin1String( "&nbsp;&nbsp;&nbsp;&nbsp;" ) );
298 //Replace multiple spaces with &nbsp;
299 //do not replace every space so we break the linebreak
300 html.replace( QRegExp( QLatin1String( "\\s\\s" ) ), QLatin1String( "&nbsp; " ) );
302 return html;
305 QString Message::plainBody() const
307 return d->body->toPlainText();
310 QString Message::escapedBody() const
312 // kDebug(14010) << escapedBody() << " " << d->richTextOverride;
314 // the escaped body is cached because QRegExp is very expensive, so it shouldn't be used any more than nescessary
315 if (!d->escapedBodyDirty)
316 return d->escapedBody;
317 else {
318 QString html;
319 if ( d->format == Qt::PlainText )
320 html = Qt::convertFromPlainText( d->body->toPlainText(), Qt::WhiteSpaceNormal );
321 else
322 html = d->body->toHtml();
324 // all this regex business is to take off the outer HTML document provided by QTextDocument
325 // remove the head
326 QRegExp badStuff ("<![^<>]*>|<head[^<>]*>.*</head[^<>]*>|</?html[^<>]*>|</?body[^<>]*>");
327 html = html.remove (badStuff);
328 // remove newlines that may be present, since they end up being displayed in the chat window. real newlines are represented with <br>, so we know \n's are meaningless
329 html = html.remove ("\n");
330 d->escapedBody = html;
331 d->escapedBodyDirty = false;
332 return html;
336 QString Message::parsedBody() const
338 //kDebug(14000) << "messageformat: " << d->format;
340 return Kopete::Emoticons::self()->theme().parseEmoticons(parseLinks(escapedBody(), Qt::RichText));
343 static QString makeRegExp( const char *pattern )
345 const QString urlChar = QLatin1String( "\\+\\-\\w\\./#@&;:=\\?~%_,\\!\\$\\*\\(\\)" );
346 const QString boundaryStart = QString( "(^|[^%1])(" ).arg( urlChar );
347 const QString boundaryEnd = QString( ")([^%1]|$)" ).arg( urlChar );
349 return boundaryStart + QLatin1String(pattern) + boundaryEnd;
352 QString Message::parseLinks( const QString &message, Qt::TextFormat format )
354 if ( format & Qt::RichText )
356 // < in HTML *always* means start-of-tag
357 QStringList entries = message.split( QChar('<'), QString::KeepEmptyParts );
359 QStringList::Iterator it = entries.begin();
361 // first one is different: it doesn't start with an HTML tag.
362 if ( it != entries.end() )
364 *it = parseLinks( *it, Qt::PlainText );
365 ++it;
368 for ( ; it != entries.end(); ++it )
370 QString curr = *it;
371 // > in HTML means start-of-tag if and only if it's the first one after a <
372 int tagclose = curr.indexOf( QChar('>') );
373 // no >: the HTML is broken, but we can cope
374 if ( tagclose == -1 )
375 continue;
376 QString tag = curr.left( tagclose + 1 );
377 QString body = curr.mid( tagclose + 1 );
378 *it = tag + parseLinks( body, Qt::PlainText );
380 return entries.join(QLatin1String("<"));
383 QString result = message;
385 // common subpatterns - may not contain matching parens!
386 const QString name = QLatin1String( "[\\w\\+\\-=_\\.]+" );
387 const QString userAndPassword = QString( "(?:%1(?::%1)?\\@)" ).arg( name );
388 const QString urlChar = QLatin1String( "\\+\\-\\w\\./#@&;:=\\?~%_,\\!\\$\\*\\(\\)" );
389 const QString urlSection = QString( "[%1]+" ).arg( urlChar );
390 const QString domain = QLatin1String( "[\\-\\w_]+(?:\\.[\\-\\w_]+)+" );
392 //Replace http/https/ftp links:
393 // Replace (stuff)://[user:password@](linkstuff) with a link
394 result.replace(
395 QRegExp( makeRegExp("\\w+://%1?\\w%2").arg( userAndPassword, urlSection ) ),
396 QLatin1String("\\1<a href=\"\\2\" title=\"\\2\">\\2</a>\\3" ) );
398 // Replace www.X.Y(linkstuff) with a http: link
399 result.replace(
400 QRegExp( makeRegExp("%1?www\\.%2%3").arg( userAndPassword, domain, urlSection ) ),
401 QLatin1String("\\1<a href=\"http://\\2\" title=\"http://\\2\">\\2</a>\\3" ) );
403 //Replace Email Links
404 // Replace user@domain with a mailto: link
405 result.replace(
406 QRegExp( makeRegExp("%1@%2").arg( name, domain ) ),
407 QLatin1String("\\1<a href=\"mailto:\\2\" title=\"mailto:\\2\">\\2</a>\\3") );
409 //Workaround for Bug 85061: Highlighted URLs adds a ' ' after the URL itself
410 // the trailing &nbsp; is included in the url.
411 result.replace( QRegExp( QLatin1String("(<a href=\"[^\"]+)(&nbsp;)(\")") ) , QLatin1String("\\1\\3") );
413 return result;
418 QDateTime Message::timestamp() const
420 return d->timeStamp;
423 void Message::setTimestamp(const QDateTime &timestamp)
425 d->timeStamp = timestamp;
428 const Contact *Message::from() const
430 return d->from;
433 QList<Contact*> Message::to() const
435 return d->to;
438 Message::MessageType Message::type() const
440 return d->type;
443 void Message::setType(MessageType type)
445 d->type = type;
448 QString Message::requestedPlugin() const
450 return d->requestedPlugin;
453 void Message::setRequestedPlugin(const QString &requestedPlugin)
455 d->requestedPlugin = requestedPlugin;
458 QColor Message::foregroundColor() const
460 return d->foregroundColor;
463 QColor Message::backgroundColor() const
465 return d->backgroundColor;
468 bool Message::isRightToLeft() const
470 return d->isRightToLeft;
473 QFont Message::font() const
475 return d->font;
478 QString Message::subject() const
480 return d->subject;
483 void Message::setSubject(const QString &subject)
485 d->subject = subject;
488 const QTextDocument *Message::body() const
490 return d->body;
493 Qt::TextFormat Message::format() const
495 return d->format;
498 Message::MessageDirection Message::direction() const
500 return d->direction;
503 void Message::setDirection(MessageDirection direction)
505 d->direction = direction;
508 Message::MessageImportance Message::importance() const
510 return d->importance;
513 ChatSession *Message::manager() const
515 return d->manager;
518 void Message::setManager(ChatSession *kmm)
520 d->manager=kmm;
523 QString Message::getHtmlStyleAttribute() const
525 QString styleAttribute;
527 styleAttribute = QString::fromUtf8("style=\"");
529 // Affect foreground(color) and background color to message.
530 if( !d->foregroundOverride && d->foregroundColor.isValid() )
532 styleAttribute += QString::fromUtf8("color: %1; ").arg(d->foregroundColor.name());
534 if( !d->backgroundOverride && d->backgroundColor.isValid() )
536 styleAttribute += QString::fromUtf8("background-color: %1; ").arg(d->backgroundColor.name());
539 // Affect font parameters.
540 if( !d->richTextOverride && d->font!=QFont() )
542 QString fontstr;
543 if(!d->font.family().isNull())
544 fontstr+=QLatin1String("font-family: ")+d->font.family()+QLatin1String("; ");
545 if(d->font.italic())
546 fontstr+=QLatin1String("font-style: italic; ");
547 if(d->font.strikeOut())
548 fontstr+=QLatin1String("text-decoration: line-through; ");
549 if(d->font.underline())
550 fontstr+=QLatin1String("text-decoration: underline; ");
551 if(d->font.bold())
552 fontstr+=QLatin1String("font-weight: bold;");
554 styleAttribute += fontstr;
557 styleAttribute += QString::fromUtf8("\"");
559 return styleAttribute;
562 // prime candidate for removal
563 #if 0
564 QString Message::decodeString( const QByteArray &message, const QTextCodec *providedCodec, bool *success )
567 Note to everyone. This function is not the most efficient, that is for sure.
568 However, it *is* the only way we can be guaranteed that a given string is
569 decoded properly.
572 if( success )
573 *success = true;
575 // Avoid heavy codec tests on empty message.
576 if( message.isEmpty() )
577 return QString::fromAscii( message );
579 //Check first 128 chars
580 int charsToCheck = message.length();
581 charsToCheck = 128 > charsToCheck ? charsToCheck : 128;
583 #ifdef __GNUC__
584 #warning Rewrite the following code: heuristicContentMatch() do not existe anymore.
585 #endif
586 //They are providing a possible codec. Check if it is valid
587 // if( providedCodec && providedCodec->heuristicContentMatch( message, charsToCheck ) >= charsToCheck )
589 //All chars decodable.
590 return providedCodec->toUnicode( message );
593 //NOTE see KEncodingDetector@kdecore
594 //Check if it is UTF
595 if( KStringHandler::isUtf8(message) )
597 //We have a UTF string almost for sure. At least we know it will be decoded.
598 return QString::fromUtf8( message );
601 //Try codecForContent - exact match
602 QTextCodec *testCodec = QTextCodec::codecForContent(message, charsToCheck);
603 if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= charsToCheck )
605 //All chars decodable.
606 return testCodec->toUnicode( message );
609 kWarning(14000) << "Unable to decode string using provided codec(s), taking best guesses!";
610 if( success )
611 *success = false;
613 //We don't have any clues here.
615 //Try local codec
616 testCodec = QTextCodec::codecForLocale();
617 if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= charsToCheck )
619 //All chars decodable.
620 kDebug(14000) << "Using locale's codec";
621 return testCodec->toUnicode( message );
624 //Try latin1 codec
625 testCodec = QTextCodec::codecForMib(4);
626 if( testCodec && testCodec->heuristicContentMatch( message, charsToCheck ) >= charsToCheck )
628 //All chars decodable.
629 kDebug(14000) << "Using latin1";
630 return testCodec->toUnicode( message );
633 kDebug(14000) << "Using latin1 and cleaning string";
634 //No codec decoded. Just decode latin1, and clean out any junk.
635 QString result = QLatin1String( message );
636 const uint length = message.length();
637 for( uint i = 0; i < length; ++i )
639 if( !result[i].isPrint() )
640 result[i] = '?';
643 return result;
645 return QString();
647 #endif
649 QStringList Message::classes() const
651 return d->classes;
654 void Message::addClass(const QString & classe)
656 d->classes.append(classe);
659 void Message::setClasses(const QStringList & classes)
661 d->classes = classes;