4 This file is part of KMail, the KDE mail client.
5 Copyright (c) 2003 Marc Mutz <mutz@kde.org>
7 KMail is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License, version 2, as
9 published by the Free Software Foundation.
11 KMail is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 In addition, as a special exception, the copyright holders give
21 permission to link the code of this program with any edition of
22 the Qt library by Trolltech AS, Norway (or with modified versions
23 of Qt that use the same license as Qt), and distribute linked
24 combinations including the two. You must obey the GNU General
25 Public License in all respects for all of the code used other than
26 Qt. If you modify this file, you may extend this exception to
27 your version of the file, but you are not obligated to do so. If
28 you do not wish to do so, delete this exception statement from
32 #include <config-messageviewer.h>
34 #include "headerstyle.h"
36 #include "headerstrategy.h"
37 #include <kpimutils/linklocator.h>
38 using KPIMUtils::LinkLocator
;
39 #include "spamheaderanalyzer.h"
40 #include "globalsettings.h"
41 #include "nodehelper.h"
42 #include "contactphotomemento.h"
44 #include <kpimutils/email.h>
46 #include <messagecore/stringutil.h>
47 #include "messagecore/globalsettings.h"
49 #include <akonadi/contact/contactsearchjob.h>
54 #include <KColorScheme>
60 #include <QApplication>
62 #include <QFontMetrics>
64 #include <kstandarddirs.h>
65 #include <KApplication>
67 #include <kmime/kmime_message.h>
68 #include <kmime/kmime_dateformatter.h>
70 using namespace MessageCore
;
72 namespace MessageViewer
{
75 // Convenience functions:
77 static inline QString
directionOf( const QString
& str
) {
78 return str
.isRightToLeft() ? "rtl" : "ltr" ;
81 // ### tmp wrapper to make kmreaderwin code working:
82 static QString
strToHtml( const QString
& str
,
83 int flags
= LinkLocator::PreserveSpaces
) {
84 return LinkLocator::convertToHtml( str
, flags
);
87 // Prepare the date string (when printing always use the localized date)
88 static QString
dateString( KMime::Message
*message
, bool printing
, bool shortDate
) {
89 const KDateTime dateTime
= message
->date()->dateTime();
90 if ( !dateTime
.isValid() )
91 return i18nc( "Unknown date", "Unknown" );
93 KLocale
* locale
= KGlobal::locale();
94 return locale
->formatDateTime( dateTime
);
97 return MessageViewer::HeaderStyle::dateShortStr( dateTime
);
99 return MessageViewer::HeaderStyle::dateStr( dateTime
);
103 static QString
subjectString( KMime::Message
*message
, int flags
= LinkLocator::PreserveSpaces
)
106 if ( message
->subject(false) ) {
107 subject
= message
->subject()->asUnicodeString();
108 if ( subject
.isEmpty() )
109 subject
= i18n("No Subject");
111 subject
= strToHtml( subject
, flags
);
113 subject
= i18n("No Subject");
118 static QString
subjectDirectionString( KMime::Message
*message
)
121 if ( message
->subject(false) )
122 subjectDir
= directionOf( NodeHelper::cleanSubject( message
) );
124 subjectDir
= directionOf( i18n("No Subject") );
129 bool HeaderStyle::hasAttachmentQuickList() const
136 // Show everything in a single line, don't show header field names.
138 class BriefHeaderStyle
: public HeaderStyle
{
139 friend class HeaderStyle
;
141 BriefHeaderStyle() : HeaderStyle() {}
142 virtual ~BriefHeaderStyle() {}
145 const char * name() const { return "brief"; }
146 HeaderStyle
* next() const { return plain(); }
147 HeaderStyle
* prev() const { return fancy(); }
149 QString
format( KMime::Message
*message
) const;
152 QString
BriefHeaderStyle::format( KMime::Message
*message
) const {
153 if ( !message
) return QString();
155 const HeaderStrategy
*strategy
= headerStrategy();
157 strategy
= HeaderStrategy::brief();
159 // The direction of the header is determined according to the direction
160 // of the application layout.
162 QString dir
= QApplication::isRightToLeft() ? "rtl" : "ltr" ;
164 // However, the direction of the message subject within the header is
165 // determined according to the contents of the subject itself. Since
166 // the "Re:" and "Fwd:" prefixes would always cause the subject to be
167 // considered left-to-right, they are ignored when determining its
170 QString subjectDir
= subjectDirectionString( message
);
172 QString headerStr
= "<div class=\"header\" dir=\"" + dir
+ "\">\n";
174 if ( strategy
->showHeader( "subject" ) ) {
175 headerStr
+= "<div dir=\"" + subjectDir
+ "\">\n"
176 "<b style=\"font-size:130%\">";
178 headerStr
+= subjectString( message
) + "</b></div>\n";
180 QStringList headerParts
;
182 if ( strategy
->showHeader( "from" ) ) {
183 /*TODO(Andras) review if it can happen or not
184 if ( fromStr.isEmpty() ) // no valid email in from, maybe just a name
185 fromStr = message->fromStrip(); // let's use that
187 QString fromPart
= StringUtil::emailAddrAsAnchor( message
->from(), StringUtil::DisplayNameOnly
);
188 if ( !vCardName().isEmpty() )
189 fromPart
+= " <a href=\"" + vCardName() + "\">" + i18n("[vCard]") + "</a>";
190 headerParts
<< fromPart
;
193 if ( strategy
->showHeader( "cc" ) && message
->cc(false) )
194 headerParts
<< i18n("CC: ") + StringUtil::emailAddrAsAnchor( message
->cc(), StringUtil::DisplayNameOnly
);
196 if ( strategy
->showHeader( "bcc" ) && message
->bcc(false) )
197 headerParts
<< i18n("BCC: ") + StringUtil::emailAddrAsAnchor( message
->bcc(), StringUtil::DisplayNameOnly
);
199 if ( strategy
->showHeader( "date" ) )
200 headerParts
<< strToHtml( dateString( message
, isPrinting(), /* shortDate = */ true ) );
202 // remove all empty (modulo whitespace) entries and joins them via ", \n"
203 headerStr
+= " (" + headerParts
.filter( QRegExp( "\\S" ) ).join( ",\n" ) + ')';
205 headerStr
+= "</div>\n";
207 // ### iterate over the rest of strategy->headerToDisplay() (or
208 // ### all headers if DefaultPolicy == Display) (elsewhere, too)
214 // show every header field on a line by itself,
215 // show subject larger
218 class PlainHeaderStyle
: public HeaderStyle
{
219 friend class HeaderStyle
;
221 PlainHeaderStyle() : HeaderStyle() {}
222 virtual ~PlainHeaderStyle() {}
225 const char * name() const { return "plain"; }
226 HeaderStyle
* next() const { return fancy(); }
227 HeaderStyle
* prev() const { return brief(); }
229 QString
format( KMime::Message
*message
) const;
232 QString
formatAllMessageHeaders( KMime::Message
*message
) const;
235 QString
PlainHeaderStyle::format( KMime::Message
*message
) const {
236 if ( !message
) return QString();
237 const HeaderStrategy
*strategy
= headerStrategy();
239 strategy
= HeaderStrategy::rich();
241 // The direction of the header is determined according to the direction
242 // of the application layout.
244 QString dir
= ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
246 // However, the direction of the message subject within the header is
247 // determined according to the contents of the subject itself. Since
248 // the "Re:" and "Fwd:" prefixes would always cause the subject to be
249 // considered left-to-right, they are ignored when determining its
252 QString subjectDir
= subjectDirectionString( message
);
256 if ( strategy
->headersToDisplay().isEmpty()
257 && strategy
->defaultPolicy() == HeaderStrategy::Display
) {
258 // crude way to emulate "all" headers - Note: no strings have
259 // i18n(), so direction should always be ltr.
260 headerStr
= QString("<div class=\"header\" dir=\"ltr\">");
261 headerStr
+= formatAllMessageHeaders( message
);
262 return headerStr
+ "</div>";
265 headerStr
= QString("<div class=\"header\" dir=\"%1\">").arg(dir
);
268 if ( strategy
->showHeader( "subject" ) )
269 headerStr
+= QString("<div dir=\"%1\"><b style=\"font-size:130%\">" +
270 subjectString( message
) + "</b></div>\n")
273 if ( strategy
->showHeader( "date" ) )
274 headerStr
.append(i18n("Date: ") + strToHtml( dateString(message
, isPrinting(), /* short = */ false ) ) + "<br/>\n" );
276 if ( strategy
->showHeader( "from" ) ) {
277 /*FIXME(Andras) review if it is still needed
278 if ( fromStr.isEmpty() ) // no valid email in from, maybe just a name
279 fromStr = message->fromStrip(); // let's use that
281 headerStr
.append( i18n("From: ") +
282 StringUtil::emailAddrAsAnchor( message
->from(), StringUtil::DisplayFullAddress
, "", StringUtil::ShowLink
) );
283 if ( !vCardName().isEmpty() )
284 headerStr
.append(" <a href=\"" + vCardName() +
285 "\">" + i18n("[vCard]") + "</a>" );
287 if ( strategy
->showHeader( "organization" )
288 && message
->headerByType("Organization"))
289 headerStr
.append(" (" +
290 strToHtml(message
->headerByType("Organization")->asUnicodeString()) + ')');
291 headerStr
.append("<br/>\n");
294 if ( strategy
->showHeader( "to" ) )
295 headerStr
.append( i18nc("To-field of the mailheader.", "To: ") +
296 StringUtil::emailAddrAsAnchor( message
->to(), StringUtil::DisplayFullAddress
) + "<br/>\n" );
298 if ( strategy
->showHeader( "cc" ) && message
->cc( false ) )
299 headerStr
.append( i18n("CC: ") +
300 StringUtil::emailAddrAsAnchor( message
->cc(), StringUtil::DisplayFullAddress
) + "<br/>\n" );
302 if ( strategy
->showHeader( "bcc" ) && message
->bcc( false ) )
303 headerStr
.append( i18n("BCC: ") +
304 StringUtil::emailAddrAsAnchor( message
->bcc(), StringUtil::DisplayFullAddress
) + "<br/>\n" );
306 if ( strategy
->showHeader( "reply-to" ) && message
->replyTo( false ) )
307 headerStr
.append( i18n("Reply to: ") +
308 StringUtil::emailAddrAsAnchor( message
->replyTo(), StringUtil::DisplayFullAddress
) + "<br/>\n" );
310 headerStr
+= "</div>\n";
315 QString
PlainHeaderStyle::formatAllMessageHeaders( KMime::Message
*message
) const {
316 QByteArray head
= message
->head();
317 KMime::Headers::Base
*header
= KMime::HeaderParsing::extractFirstHeader( head
);
320 result
+= strToHtml( QLatin1String(header
->type()) + QLatin1String(": ") + header
->asUnicodeString() );
321 result
+= QLatin1String( "<br />\n" );
323 header
= KMime::HeaderParsing::extractFirstHeader( head
);
331 // Like PlainHeaderStyle, but with slick frames and background colours.
334 class FancyHeaderStyle
: public HeaderStyle
{
335 friend class HeaderStyle
;
337 FancyHeaderStyle() : HeaderStyle() {}
338 virtual ~FancyHeaderStyle() {}
341 const char * name() const { return "fancy"; }
342 HeaderStyle
* next() const { return enterprise(); }
343 HeaderStyle
* prev() const { return plain(); }
345 QString
format( KMime::Message
*message
) const;
347 virtual bool hasAttachmentQuickList() const {
351 static QString
imgToDataUrl( const QImage
& image
);
354 static QString
drawSpamMeter( SpamError spamError
, double percent
, double confidence
,
355 const QString
& filterHeader
, const QString
& confidenceHeader
);
358 QString
FancyHeaderStyle::drawSpamMeter( SpamError spamError
, double percent
, double confidence
,
359 const QString
& filterHeader
, const QString
& confidenceHeader
)
361 static const int meterWidth
= 20;
362 static const int meterHeight
= 5;
363 QImage
meterBar( meterWidth
, 1, QImage::Format_Indexed8
/*QImage::Format_RGB32*/ );
364 meterBar
.setNumColors( 24 );
366 const unsigned short gradient
[meterWidth
][3] = {
388 meterBar
.setColor( meterWidth
+ 1, qRgb( 255, 255, 255 ) );
389 meterBar
.setColor( meterWidth
+ 2, qRgb( 170, 170, 170 ) );
390 if ( spamError
!= noError
) // grey is for errors
391 meterBar
.fill( meterWidth
+ 2 );
393 meterBar
.fill( meterWidth
+ 1 );
394 const int max
= qMin( meterWidth
, static_cast<int>( percent
) / 5 );
395 for ( int i
= 0; i
< max
; ++i
) {
396 meterBar
.setColor( i
+1, qRgb( gradient
[i
][0], gradient
[i
][1],
398 meterBar
.setPixel( i
, 0, i
+1 );
403 QString confidenceString
;
404 if ( spamError
== noError
)
406 if ( confidence
>= 0 )
408 confidenceString
= QString::number( confidence
) + "% ";
409 titleText
= i18n("%1% probability of being spam with confidence %3%.\n\n"
410 "Full report:\nProbability=%2\nConfidence=%4",
411 QString::number(percent
,'f',2), filterHeader
, confidence
, confidenceHeader
);
413 else // do not show negative confidence
415 confidenceString
= QString() + " ";
416 titleText
= i18n("%1% probability of being spam.\n\n"
417 "Full report:\nProbability=%2",
418 QString::number(percent
,'f',2), filterHeader
);
426 case errorExtractingAgentString
:
427 errorMsg
= i18n( "No Spam agent" );
429 case couldNotConverScoreToFloat
:
430 errorMsg
= i18n( "Spam filter score not a number" );
432 case couldNotConvertThresholdToFloatOrThresholdIsNegative
:
433 errorMsg
= i18n( "Threshold not a valid number" );
435 case couldNotFindTheScoreField
:
436 errorMsg
= i18n( "Spam filter score could not be extracted from header" );
438 case couldNotFindTheThresholdField
:
439 errorMsg
= i18n( "Threshold could not be extracted from header" );
442 errorMsg
= i18n( "Error evaluating spam score" );
445 // report the error in the spam filter
446 titleText
= i18n("%1.\n\n"
448 errorMsg
, filterHeader
);
450 return QString("<img src=\"%1\" width=\"%2\" height=\"%3\" style=\"border: 1px solid black;\" title=\"%4\"> ")
451 .arg( imgToDataUrl( meterBar
), QString::number( meterWidth
),
452 QString::number( meterHeight
), titleText
) + confidenceString
;
456 QString
FancyHeaderStyle::format( KMime::Message
*message
) const {
457 if ( !message
) return QString();
458 const HeaderStrategy
*strategy
= headerStrategy();
460 strategy
= HeaderStrategy::rich();
462 // ### from kmreaderwin begin
463 // The direction of the header is determined according to the direction
464 // of the application layout.
466 QString dir
= ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
467 QString headerStr
= QString::fromLatin1("<div class=\"fancy header\" dir=\"%1\">\n").arg(dir
);
469 // However, the direction of the message subject within the header is
470 // determined according to the contents of the subject itself. Since
471 // the "Re:" and "Fwd:" prefixes would always cause the subject to be
472 // considered left-to-right, they are ignored when determining its
475 QString subjectDir
= subjectDirectionString( message
);
477 // Spam header display.
478 // If the spamSpamStatus config value is true then we look for headers
479 // from a few spam filters and try to create visually meaningful graphics
480 // out of the spam scores.
484 if ( GlobalSettings::self()->showSpamStatus() ) {
485 const SpamScores scores
= SpamHeaderAnalyzer::getSpamScores( message
);
487 for ( SpamScores::const_iterator it
= scores
.constBegin(), end
= scores
.constEnd() ; it
!= end
; ++it
)
488 spamHTML
+= (*it
).agent() + ' ' +
489 drawSpamMeter( (*it
).error(), (*it
).score(), (*it
).confidence(), (*it
).spamHeader(), (*it
).confidenceHeader() );
496 int photoHeight
= 60;
497 bool useOtherPhotoSources
= false;
499 if ( allowAsync() ) {
501 Q_ASSERT( nodeHelper() );
502 Q_ASSERT( sourceObject() );
504 ContactPhotoMemento
*photoMemento
=
505 dynamic_cast<ContactPhotoMemento
*>( nodeHelper()->bodyPartMemento( message
, "contactphoto" ) );
506 if ( !photoMemento
) {
507 const QString email
= KPIMUtils::firstEmailAddress( message
->from()->asUnicodeString() );
508 photoMemento
= new ContactPhotoMemento( email
);
509 nodeHelper()->setBodyPartMemento( message
, "contactphoto", photoMemento
);
510 QObject::connect( photoMemento
, SIGNAL(update(MessageViewer::Viewer::UpdateMode
)),
511 sourceObject(), SLOT(update(MessageViewer::Viewer::UpdateMode
)) );
514 if ( photoMemento
->finished() ) {
516 useOtherPhotoSources
= true;
517 if ( photoMemento
->photo().isIntern() )
519 // get photo data and convert to data: url
520 QImage photo
= photoMemento
->photo().data();
521 if ( !photo
.isNull() )
523 photoWidth
= photo
.width();
524 photoHeight
= photo
.height();
525 // scale below 60, otherwise it can get way too large
526 if ( photoHeight
> 60 ) {
527 double ratio
= ( double )photoHeight
/ ( double )photoWidth
;
529 photoWidth
= (int)( 60 / ratio
);
530 photo
= photo
.scaled( photoWidth
, photoHeight
, Qt::IgnoreAspectRatio
, Qt::SmoothTransformation
);
532 photoURL
= imgToDataUrl( photo
);
537 photoURL
= photoMemento
->photo().url();
538 if ( photoURL
.startsWith('/') )
539 photoURL
.prepend( "file:" );
542 // if the memento is not finished yet, use other photo sources instead
543 useOtherPhotoSources
= true;
546 useOtherPhotoSources
= true;
549 if( photoURL
.isEmpty() && message
->headerByType( "Face" ) && useOtherPhotoSources
) {
550 // no photo, look for a Face header
551 QString faceheader
= message
->headerByType( "Face" )->asUnicodeString();
552 if ( !faceheader
.isEmpty() ) {
554 kDebug() << "Found Face: header";
556 QByteArray facestring
= faceheader
.toUtf8();
557 // Spec says header should be less than 998 bytes
558 // Face: is 5 characters
559 if ( facestring
.length() < 993 ) {
560 QByteArray facearray
= QByteArray::fromBase64( facestring
);
563 if ( faceimage
.loadFromData( facearray
, "png" ) ) {
564 // Spec says image must be 48x48 pixels
565 if ( ( 48 == faceimage
.width() ) && ( 48 == faceimage
.height() ) ) {
566 photoURL
= imgToDataUrl( faceimage
);
570 kDebug() << "Face: header image is" << faceimage
.width() << "by"
571 << faceimage
.height() << "not 48x48 Pixels";
574 kDebug() << "Failed to load decoded png from Face: header";
577 kDebug() << "Face: header too long at" << facestring
.length();
582 if( photoURL
.isEmpty() && message
->headerByType( "X-Face" ) && useOtherPhotoSources
)
584 // no photo, look for a X-Face header
585 const QString xfhead
= message
->headerByType( "X-Face" )->asUnicodeString();
586 if ( !xfhead
.isEmpty() )
588 MessageViewer::KXFace xf
;
589 photoURL
= imgToDataUrl( xf
.toImage( xfhead
) );
596 if( !photoURL
.isEmpty() )
598 //kDebug() << "Got a photo:" << photoURL;
599 userHTML
= QString::fromLatin1("<img src=\"%1\" width=\"%2\" height=\"%3\">")
600 .arg( photoURL
).arg( photoWidth
).arg( photoHeight
);
601 userHTML
= QString("<div class=\"senderpic\">") + userHTML
+ "</div>";
604 // the subject line and box below for details
605 if ( strategy
->showHeader( "subject" ) ) {
606 const int flags
= LinkLocator::PreserveSpaces
|
607 ( GlobalSettings::self()->showEmoticons() ?
608 LinkLocator::ReplaceSmileys
: 0 );
610 headerStr
+= QString::fromLatin1("<div dir=\"%1\">%2</div>\n")
612 .arg( subjectString( message
, flags
) );
614 headerStr
+= "<table class=\"outer\"><tr><td width=\"100%\"><table>\n";
615 //headerStr += "<table>\n";
617 // the mailto: URLs can contain %3 etc., therefore usage of multiple
618 // QString::arg is not possible
619 if ( strategy
->showHeader( "from" ) ) {
621 // Get the resent-from header into a Mailbox
622 QList
<KMime::Types::Mailbox
> resentFrom
;
623 if ( message
->headerByType( "Resent-From" ) ) {
624 const QByteArray data
= message
->headerByType( "Resent-From" )->as7BitString( false );
625 const char * start
= data
.data();
626 const char * end
= start
+ data
.length();
627 KMime::Types::AddressList addressList
;
628 KMime::HeaderParsing::parseAddressList( start
, end
, addressList
);
629 foreach ( const KMime::Types::Address
&addr
, addressList
) {
630 foreach ( const KMime::Types::Mailbox
&mbox
, addr
.mailboxList
) {
631 resentFrom
.append( mbox
);
636 headerStr
+= QString::fromLatin1("<tr><th>%1</th>\n"
639 + StringUtil::emailAddrAsAnchor( message
->from(), StringUtil::DisplayFullAddress
)
640 + ( message
->headerByType( "Resent-From" ) ? " "
641 + i18n( "(resent from %1)",
642 StringUtil::emailAddrAsAnchor(
643 resentFrom
, StringUtil::DisplayFullAddress
) )
645 + ( !vCardName().isEmpty() ? " <a href=\"" + vCardName() + "\">"
646 + i18n("[vCard]") + "</a>"
648 + ( !message
->headerByType("Organization")
651 + strToHtml(message
->headerByType("Organization")->asUnicodeString())
656 if ( strategy
->showHeader( "to" ) )
657 headerStr
.append(QString::fromLatin1("<tr><th>%1</th>\n"
658 "<td>%2</td></tr>\n")
659 .arg( i18nc( "To-field of the mail header.","To: " ) )
660 .arg( StringUtil::emailAddrAsAnchor( message
->to(), StringUtil::DisplayFullAddress
,
661 QString(), StringUtil::ShowLink
, StringUtil::ExpandableAddresses
,
663 GlobalSettings::self()->numberOfAddressesToShow() ) ) );
666 if ( strategy
->showHeader( "cc" ) && message
->cc(false))
667 headerStr
.append(QString::fromLatin1("<tr><th>%1</th>\n"
668 "<td>%2</td></tr>\n")
669 .arg( i18n( "CC: " ) )
670 .arg( StringUtil::emailAddrAsAnchor(message
->cc(), StringUtil::DisplayFullAddress
,
671 QString(), StringUtil::ShowLink
, StringUtil::ExpandableAddresses
,
673 GlobalSettings::self()->numberOfAddressesToShow() ) ) );
676 if ( strategy
->showHeader( "bcc" ) && message
->bcc(false))
677 headerStr
.append(QString::fromLatin1("<tr><th>%1</th>\n"
678 "<td>%2</td></tr>\n")
679 .arg( i18n( "BCC: " ) )
680 .arg( StringUtil::emailAddrAsAnchor( message
->bcc(), StringUtil::DisplayFullAddress
) ) );
682 if ( strategy
->showHeader( "date" ) )
683 headerStr
.append(QString::fromLatin1("<tr><th>%1</th>\n"
684 "<td dir=\"%2\">%3</td></tr>\n")
686 .arg( directionOf( dateStr( message
->date()->dateTime() ) ) )
687 .arg(strToHtml( dateString( message
, isPrinting(), /* short = */ false ) ) ) );
688 if ( GlobalSettings::self()->showUserAgent() ) {
689 if ( strategy
->showHeader( "user-agent" ) ) {
690 if ( message
->headerByType("User-Agent") ) {
691 headerStr
.append(QString::fromLatin1("<tr><th>%1</th>\n"
692 "<td>%2</td></tr>\n")
693 .arg(i18n("User-Agent: "))
694 .arg( strToHtml( message
->headerByType("User-Agent")->as7BitString() ) ) );
698 if ( strategy
->showHeader( "x-mailer" ) ) {
699 if ( message
->headerByType("X-Mailer") ) {
700 headerStr
.append(QString::fromLatin1("<tr><th>%1</th>\n"
701 "<td>%2</td></tr>\n")
702 .arg(i18n("X-Mailer: "))
703 .arg( strToHtml( message
->headerByType("X-Mailer")->as7BitString() ) ) );
707 headerStr
.append( QString( "<tr><td colspan=\"2\"><div id=\"attachmentInjectionPoint\"></div></td></tr>" ) );
709 QString::fromLatin1( "</table></td><td align=\"center\">%1</td></tr></table>\n" ).arg(userHTML
) );
711 if ( !spamHTML
.isEmpty() )
712 headerStr
.append( QString::fromLatin1( "<div class=\"spamheader\" dir=\"%1\"><b>%2</b> <span style=\"padding-left: 20px;\">%3</span></div>\n")
713 .arg( subjectDir
, i18n("Spam Status:"), spamHTML
) );
715 headerStr
+= "</div>\n\n";
719 QString
FancyHeaderStyle::imgToDataUrl( const QImage
&image
)
722 QBuffer
buffer( &ba
);
723 buffer
.open( QIODevice::WriteOnly
);
724 image
.save( &buffer
, "PNG" );
725 return QString::fromLatin1("data:image/%1;base64,%2")
726 .arg( QString::fromLatin1( "PNG" ), QString::fromLatin1( ba
.toBase64() ) );
729 // #####################
731 class EnterpriseHeaderStyle
: public HeaderStyle
{
732 friend class HeaderStyle
;
734 EnterpriseHeaderStyle() : HeaderStyle() {}
735 virtual ~EnterpriseHeaderStyle() {}
738 const char * name() const { return "enterprise"; }
739 HeaderStyle
* next() const {
740 #if defined KDEPIM_MOBILE_UI
746 HeaderStyle
* prev() const { return fancy(); }
748 QString
format( KMime::Message
*message
) const;
750 virtual bool hasAttachmentQuickList() const {
755 QString
EnterpriseHeaderStyle::format( KMime::Message
*message
) const
757 if ( !message
) return QString();
758 const HeaderStrategy
*strategy
= headerStrategy();
760 strategy
= HeaderStrategy::brief();
763 // The direction of the header is determined according to the direction
764 // of the application layout.
766 QString dir
= ( QApplication::layoutDirection() == Qt::RightToLeft
) ?
769 // However, the direction of the message subject within the header is
770 // determined according to the contents of the subject itself. Since
771 // the "Re:" and "Fwd:" prefixes would always cause the subject to be
772 // considered left-to-right, they are ignored when determining its
775 QString subjectDir
= subjectDirectionString( message
);
777 // colors depend on if it is encapsulated or not
778 QColor
fontColor( Qt::white
);
779 QString linkColor
= "class =\"white\"";
780 const QColor activeColor
= KColorScheme( QPalette::Active
, KColorScheme::Selection
).
781 background().color();
782 QColor activeColorDark
= activeColor
.dark(130);
783 // reverse colors for encapsulated
785 activeColorDark
= activeColor
.dark(50);
786 fontColor
= QColor(Qt::black
);
787 linkColor
= "class =\"black\"";
790 QString
imgpath( KStandardDirs::locate("data","libmessageviewer/pics/") );
791 imgpath
.prepend( "file:///" );
792 imgpath
.append("enterprise_");
793 const QString
borderSettings( " padding-top: 0px; padding-bottom: 0px; border-width: 0px " );
799 "<div style=\"position: fixed; top: 0px; left: 0px; background-color: #606060; "
800 "width: 10px; min-height: 100%;\"> </div>"
801 "<div style=\"position: fixed; top: 0px; right: 0px; background-color: #606060; "
802 "width: 10px; min-height: 100%;\"> </div>";
805 "<div style=\"margin-left: 10px; top: 0px;\"><span style=\"font-size: 10px; font-weight: bold;\">"
806 + dateString( message
, isPrinting(), /* shortDate */ false ) + "</span></div>"
808 "<table style=\"background: "+activeColorDark
.name()+"; border-collapse:collapse; top: 14px; min-width: 200px; \" cellpadding=0> \n"
810 " <td style=\"min-width: 6px; background-image: url("+imgpath
+"top_left.png); \"></td> \n"
811 " <td style=\"height: 6px; width: 100%; background: url("+imgpath
+"top.png); \"></td> \n"
812 " <td style=\"min-width: 6px; background: url("+imgpath
+"top_right.png); \"></td> </tr> \n"
814 " <td style=\"min-width: 6px; max-width: 6px; background: url("+imgpath
+"left.png); \"></td> \n"
815 " <td style=\"\"> \n";
817 "<div class=\"noprint\" style=\"z-index: 1; float:right; position: relative; top: -35px; right: 20px ; max-height: 65px\">\n"
818 "<img src=\"" + imgpath
+ "icon.png\">\n"
821 " <table style=\"color: "+fontColor
.name()+" ! important; margin: 1px; border-spacing: 0px;\" cellpadding=0> \n";
824 if ( strategy
->showHeader( "subject" ) ) {
827 " <td style=\"font-size: 6px; text-align: right; padding-left: 5px; padding-right: 24px; "+borderSettings
+"\"></td> \n"
828 " <td style=\"font-weight: bolder; font-size: 120%; padding-right: 91px; "+borderSettings
+"\">";
829 headerStr
+= subjectString( message
)+ "</td> \n"
834 if ( strategy
->showHeader( "from" ) ) {
836 // We by design use the stripped mail address here, it is more enterprise-like.
837 QString fromPart
= StringUtil::emailAddrAsAnchor( message
->from(),
838 StringUtil::DisplayNameOnly
, linkColor
);
839 if ( !vCardName().isEmpty() )
840 fromPart
+= " <a href=\"" + vCardName() + "\" "+linkColor
+">" + i18n("[vCard]") + "</a>";
842 //if ( strategy->showHeader( "date" ) )
845 " <td style=\"font-size: 10px; padding-left: 5px; padding-right: 24px; text-align: right; "+borderSettings
+"\">"+i18n("From: ")+"</td> \n"
846 " <td style=\""+borderSettings
+"\">"+ fromPart
+"</td> "
851 if ( strategy
->showHeader( "to" ) ) {
854 " <td style=\"font-size: 10px; text-align: right; padding-left: 5px; padding-right: 24px; " + borderSettings
+ "\">" + i18n("To: ") + "</td> "
855 " <td style=\"" + borderSettings
+ "\">" +
856 StringUtil::emailAddrAsAnchor( message
->to(), StringUtil::DisplayFullAddress
, linkColor
) +
862 if ( strategy
->showHeader( "cc" ) && message
->cc( false ) ) {
865 " <td style=\"font-size: 10px; text-align: right; padding-left: 5px; padding-right: 24px; " + borderSettings
+ "\">" + i18n("CC: ") + "</td> "
866 " <td style=\"" + borderSettings
+ "\">" +
867 StringUtil::emailAddrAsAnchor( message
->cc(), StringUtil::DisplayFullAddress
, linkColor
) +
873 if ( strategy
->showHeader( "bcc" ) && message
->bcc( false ) ) {
876 " <td style=\"font-size: 10px; text-align: right; padding-left: 5px; padding-right: 24px; " + borderSettings
+ "\">" + i18n("BCC: ") + "</td> "
877 " <td style=\"" + borderSettings
+ "\">" +
878 StringUtil::emailAddrAsAnchor( message
->bcc(), StringUtil::DisplayFullAddress
, linkColor
) +
887 " <td style=\"min-width: 6px; max-height: 15px; background: url("+imgpath
+"right.png); \"></td> \n"
890 " <td style=\"min-width: 6px; background: url("+imgpath
+"s_left.png); \"></td> \n"
891 " <td style=\"height: 35px; width: 80%; background: url("+imgpath
+"sbar.png);\"> \n"
892 " <img src=\""+imgpath
+"sw.png\" style=\"margin: 0px; height: 30px; overflow:hidden; \"> \n"
893 " <img src=\""+imgpath
+"sp_right.png\" style=\"float: right; \"> </td> \n"
894 " <td style=\"min-width: 6px; background: url("+imgpath
+"s_right.png); \"></td> \n"
903 "<div class=\"noprint\" style=\"position: absolute; top: 60px; right: 20px;\">"
904 "<div id=\"attachmentInjectionPoint\"></div>"
908 if ( isPrinting() ) {
909 //provide a bit more left padding when printing
910 //kolab/issue3254 (printed mail cut at the left side)
911 headerStr
+= "<div style=\"padding: 6px; padding-left: 10px;\">";
913 headerStr
+= "<div style=\"padding: 6px;\">";
918 // ### iterate over the rest of strategy->headerToDisplay() (or
919 // ### all headers if DefaultPolicy == Display) (elsewhere, too)
923 // #####################
925 class MobileHeaderStyle
: public HeaderStyle
{
926 friend class HeaderStyle
;
928 MobileHeaderStyle() : HeaderStyle() {}
929 virtual ~MobileHeaderStyle() {}
932 const char * name() const { return "mobile"; }
933 HeaderStyle
* next() const { return mobileExtended(); }
934 HeaderStyle
* prev() const { return enterprise(); }
936 QString
format( KMime::Message
*message
) const;
939 class MobileExtendedHeaderStyle
: public HeaderStyle
{
940 friend class HeaderStyle
;
942 MobileExtendedHeaderStyle() : HeaderStyle() {}
943 virtual ~MobileExtendedHeaderStyle() {}
946 const char * name() const { return "mobileExtended"; }
947 HeaderStyle
* next() const { return brief(); }
948 HeaderStyle
* prev() const { return mobile(); }
950 QString
format( KMime::Message
*message
) const;
953 static int matchingFontSize( const QString
&text
, int maximumWidth
, int fontPixelSize
)
955 int pixelSize
= fontPixelSize
;
957 if ( pixelSize
<= 8 )
961 font
.setPixelSize( pixelSize
);
962 QFontMetrics
fm( font
);
963 if ( fm
.width( text
) <= maximumWidth
)
972 static QString
formatMobileHeader( KMime::Message
*message
, bool extendedFormat
, const HeaderStyle
*style
)
978 QString linkColor
="style=\"color: #0E49A1; text-decoration: none\"";
979 QString fromPart
= StringUtil::emailAddrAsAnchor( message
->from(), StringUtil::DisplayFullAddress
, linkColor
);
981 if ( !style
->vCardName().isEmpty() )
982 fromPart
+= " <a href=\"" + style
->vCardName() + "\" " + linkColor
+ ">" + i18n( "[vCard]" ) + "</a>";
984 const QString toPart
= StringUtil::emailAddrAsAnchor( message
->to(), StringUtil::DisplayFullAddress
, linkColor
);
985 const QString ccPart
= StringUtil::emailAddrAsAnchor( message
->cc(), StringUtil::DisplayFullAddress
, linkColor
);
988 const QString
imagePath( QLatin1String( "file:///" ) + KStandardDirs::locate( "data", "libmessageviewer/pics/" ) );
989 const QString
mobileImagePath( imagePath
+ QLatin1String( "mobile_" ) );
990 const QString
mobileExtendedImagePath( imagePath
+ QLatin1String( "mobileextended_" ) );
992 const Akonadi::MessageStatus status
= style
->messageStatus();
994 if ( status
.isImportant() )
995 flagsPart
+= "<img src=\"" + mobileImagePath
+ "status_important.png\" height=\"22\" width=\"22\"/>";
996 if ( status
.hasAttachment() )
997 flagsPart
+= "<img src=\"" + mobileImagePath
+ "status_attachment.png\" height=\"22\" width=\"22\"/>";
998 if ( status
.isToAct() )
999 flagsPart
+= "<img src=\"" + mobileImagePath
+ "status_actionitem.png\" height=\"22\" width=\"22\"/>";
1000 if ( status
.isReplied() )
1001 flagsPart
+= "<img src=\"" + mobileImagePath
+ "status_replied.png\" height=\"22\" width=\"22\"/>";
1002 if ( status
.isForwarded() )
1003 flagsPart
+= "<img src=\"" + mobileImagePath
+ "status_forwarded.png\" height=\"22\" width=\"22\"/>";
1004 if ( status
.isSigned() )
1005 flagsPart
+= "<img src=\"" + mobileImagePath
+ "status_signed.png\" height=\"22\" width=\"22\"/>";
1006 if ( status
.isEncrypted() )
1007 flagsPart
+= "<img src=\"" + mobileImagePath
+ "status_encrypted.png\" height=\"22\" width=\"22\"/>";
1011 headerStr
+= "<div style=\"width: 100%\">\n";
1012 headerStr
+= " <table width=\"100%\" bgcolor=\"#B4E3F7\">\n";
1013 headerStr
+= " <tr>\n";
1014 headerStr
+= " <td valign=\"bottom\" width=\"80%\">\n";
1015 headerStr
+= " <div style=\"text-align: left; font-size: 20px; color: #0E49A1\">" + fromPart
+ "</div>\n";
1016 headerStr
+= " </td>\n";
1017 headerStr
+= " <td valign=\"bottom\" width=\"20%\" align=\"right\">\n";
1018 headerStr
+= " <div style=\"text-align: right; color: #0E49A1;\">" + flagsPart
+ "</div>\n";
1019 headerStr
+= " </td>\n";
1020 headerStr
+= " </tr>\n";
1021 headerStr
+= " </table>\n";
1022 headerStr
+= " <table width=\"100%\" bgcolor=\"#B4E3F7\">\n";
1023 if ( extendedFormat
) {
1024 headerStr
+= " <tr>\n";
1025 headerStr
+= " <td valign=\"bottom\" colspan=\"2\">\n";
1026 headerStr
+= " <div style=\"height: 20px; font-size: 15px; color: #0E49A1\">" + toPart
+ ccPart
+ "</div>\n";
1027 headerStr
+= " </td>\n";
1028 headerStr
+= " </tr>\n";
1031 const int subjectFontSize
= matchingFontSize( message
->subject()->asUnicodeString(), 650, 20 );
1032 headerStr
+= " <tr>\n";
1033 headerStr
+= " <td valign=\"bottom\" colspan=\"2\">\n";
1034 headerStr
+= " <div style=\"height: 35px; font-size: " + QString::number( subjectFontSize
) + "px; color: #24353F;\">" + message
->subject()->asUnicodeString() + "</div>\n";
1035 headerStr
+= " </td>\n";
1036 headerStr
+= " </tr>\n";
1037 headerStr
+= " <tr>\n";
1038 headerStr
+= " <td align=\"left\" width=\"50%\">\n";
1039 if ( !style
->messagePath().isEmpty() ) {
1040 headerStr
+= " <div style=\"font-size: 15px; color: #24353F\">" + style
->messagePath() + "</div>\n";
1042 headerStr
+= " </td>\n";
1043 headerStr
+= " <td align=\"right\" width=\"50%\">\n";
1044 headerStr
+= " <div style=\"font-size: 15px; color: #24353F; text-align: right; margin-right: 15px\">" + i18n( "sent: " );
1045 headerStr
+= dateString( message
, style
->isPrinting(), /* shortDate = */ false ) + "</div>\n";
1046 headerStr
+= " </td>\n";
1047 headerStr
+= " </tr>\n";
1048 headerStr
+= " </table>\n";
1049 headerStr
+= " <br/>\n";
1050 headerStr
+= "</div>\n";
1055 QString
MobileHeaderStyle::format( KMime::Message
*message
) const
1057 return formatMobileHeader( message
, false, this );
1060 QString
MobileExtendedHeaderStyle::format( KMime::Message
*message
) const
1062 return formatMobileHeader( message
, true, this );
1065 // #####################
1068 // HeaderStyle abstract base:
1071 HeaderStyle::HeaderStyle()
1072 : mStrategy( 0 ), mPrinting( false ), mTopLevel( true ), mNodeHelper( 0 ), mAllowAsync( false ),
1077 HeaderStyle::~HeaderStyle() {
1081 HeaderStyle
* HeaderStyle::create( Type type
) {
1083 case Brief
: return brief();
1084 case Plain
: return plain();
1085 case Fancy
: return fancy();
1086 case Enterprise
: return enterprise();
1087 case Mobile
: return mobile();
1089 kFatal() << "Unknown header style ( type ==" << (int)type
<< ") requested!";
1090 return 0; // make compiler happy
1093 HeaderStyle
* HeaderStyle::create( const QString
& type
) {
1094 const QString lowerType
= type
.toLower();
1095 if ( lowerType
== QLatin1String("brief") ) return brief();
1096 if ( lowerType
== QLatin1String("plain") ) return plain();
1097 if ( lowerType
== QLatin1String("enterprise") ) return enterprise();
1098 if ( lowerType
== QLatin1String("mobile") ) return mobile();
1099 if ( lowerType
== QLatin1String( "mobileExtended") ) return mobileExtended();
1100 //if ( lowerType == "fancy" ) return fancy(); // not needed, see below
1101 // don't kFatal here, b/c the strings are user-provided
1102 // (KConfig), so fail gracefully to the default:
1106 HeaderStyle
* briefStyle
= 0;
1107 HeaderStyle
* plainStyle
= 0;
1108 HeaderStyle
* fancyStyle
= 0;
1109 HeaderStyle
* enterpriseStyle
= 0;
1110 HeaderStyle
* mobileStyle
= 0;
1111 HeaderStyle
* mobileExtendedStyle
= 0;
1113 HeaderStyle
* HeaderStyle::brief() {
1115 briefStyle
= new BriefHeaderStyle();
1119 HeaderStyle
* HeaderStyle::plain() {
1121 plainStyle
= new PlainHeaderStyle();
1125 HeaderStyle
* HeaderStyle::fancy() {
1127 fancyStyle
= new FancyHeaderStyle();
1131 HeaderStyle
* HeaderStyle::enterprise() {
1132 if ( !enterpriseStyle
)
1133 enterpriseStyle
= new EnterpriseHeaderStyle();
1134 return enterpriseStyle
;
1137 HeaderStyle
* HeaderStyle::mobile() {
1139 mobileStyle
= new MobileHeaderStyle();
1143 HeaderStyle
* HeaderStyle::mobileExtended() {
1144 if ( !mobileExtendedStyle
)
1145 mobileExtendedStyle
= new MobileExtendedHeaderStyle
;
1146 return mobileExtendedStyle
;
1149 QString
HeaderStyle::dateStr(const KDateTime
&dateTime
)
1151 const time_t unixTime
= dateTime
.toTime_t();
1152 return KMime::DateFormatter::formatDate(
1153 static_cast<KMime::DateFormatter::FormatType
>(
1154 MessageCore::GlobalSettings::self()->dateFormat() ),
1155 unixTime
, MessageCore::GlobalSettings::self()->customDateFormat() );
1158 QString
HeaderStyle::dateShortStr(const KDateTime
&dateTime
)
1160 return KGlobal::locale()->formatDateTime( dateTime
, KLocale::FancyShortDate
);