kilobyte is kB not kb
[kdepim.git] / messageviewer / headerstyle.cpp
blob241371935eff888e2d791a1de98216d11a4bfcbc
1 /* -*- c++ -*-
2 headerstyle.cpp
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
29 your version.
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>
45 #include "kxface.h"
46 #include <messagecore/stringutil.h>
47 #include "messagecore/globalsettings.h"
49 #include <akonadi/contact/contactsearchjob.h>
50 #include <kdebug.h>
51 #include <klocale.h>
52 #include <kglobal.h>
53 #include <kcodecs.h>
54 #include <KColorScheme>
56 #include <KDateTime>
57 #include <QBuffer>
58 #include <QBitmap>
59 #include <QImage>
60 #include <QApplication>
61 #include <QRegExp>
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" );
92 if( printing ) {
93 KLocale * locale = KGlobal::locale();
94 return locale->formatDateTime( dateTime );
95 } else {
96 if ( shortDate )
97 return MessageViewer::HeaderStyle::dateShortStr( dateTime );
98 else
99 return MessageViewer::HeaderStyle::dateStr( dateTime );
103 static QString subjectString( KMime::Message *message, int flags = LinkLocator::PreserveSpaces )
105 QString subject;
106 if ( message->subject(false) ) {
107 subject = message->subject()->asUnicodeString();
108 if ( subject.isEmpty() )
109 subject = i18n("No Subject");
110 else
111 subject = strToHtml( subject, flags );
112 } else {
113 subject = i18n("No Subject");
115 return subject;
118 static QString subjectDirectionString( KMime::Message *message )
120 QString subjectDir;
121 if ( message->subject(false) )
122 subjectDir = directionOf( NodeHelper::cleanSubject( message ) );
123 else
124 subjectDir = directionOf( i18n("No Subject") );
125 return subjectDir;
129 bool HeaderStyle::hasAttachmentQuickList() const
131 return false;
135 // BriefHeaderStyle
136 // Show everything in a single line, don't show header field names.
138 class BriefHeaderStyle : public HeaderStyle {
139 friend class HeaderStyle;
140 protected:
141 BriefHeaderStyle() : HeaderStyle() {}
142 virtual ~BriefHeaderStyle() {}
144 public:
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();
156 if ( !strategy )
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
168 // direction.
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 += "&nbsp;&nbsp;<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)
209 return headerStr;
213 // PlainHeaderStyle:
214 // show every header field on a line by itself,
215 // show subject larger
218 class PlainHeaderStyle : public HeaderStyle {
219 friend class HeaderStyle;
220 protected:
221 PlainHeaderStyle() : HeaderStyle() {}
222 virtual ~PlainHeaderStyle() {}
224 public:
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;
231 private:
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();
238 if ( !strategy )
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
250 // direction.
252 QString subjectDir = subjectDirectionString( message );
254 QString headerStr;
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);
267 //case HdrLong:
268 if ( strategy->showHeader( "subject" ) )
269 headerStr += QString("<div dir=\"%1\"><b style=\"font-size:130%\">" +
270 subjectString( message ) + "</b></div>\n")
271 .arg(subjectDir);
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("&nbsp;&nbsp;<a href=\"" + vCardName() +
285 "\">" + i18n("[vCard]") + "</a>" );
287 if ( strategy->showHeader( "organization" )
288 && message->headerByType("Organization"))
289 headerStr.append("&nbsp;&nbsp;(" +
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";
312 return headerStr;
315 QString PlainHeaderStyle::formatAllMessageHeaders( KMime::Message *message ) const {
316 QByteArray head = message->head();
317 KMime::Headers::Base *header = KMime::HeaderParsing::extractFirstHeader( head );
318 QString result;
319 while ( header ) {
320 result += strToHtml( QLatin1String(header->type()) + QLatin1String(": ") + header->asUnicodeString() );
321 result += QLatin1String( "<br />\n" );
322 delete header;
323 header = KMime::HeaderParsing::extractFirstHeader( head );
326 return result;
330 // FancyHeaderStyle:
331 // Like PlainHeaderStyle, but with slick frames and background colours.
334 class FancyHeaderStyle : public HeaderStyle {
335 friend class HeaderStyle;
336 protected:
337 FancyHeaderStyle() : HeaderStyle() {}
338 virtual ~FancyHeaderStyle() {}
340 public:
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 {
348 return true;
351 static QString imgToDataUrl( const QImage & image );
353 private:
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] = {
367 { 0, 255, 0 },
368 { 27, 254, 0 },
369 { 54, 252, 0 },
370 { 80, 250, 0 },
371 { 107, 249, 0 },
372 { 135, 247, 0 },
373 { 161, 246, 0 },
374 { 187, 244, 0 },
375 { 214, 242, 0 },
376 { 241, 241, 0 },
377 { 255, 228, 0 },
378 { 255, 202, 0 },
379 { 255, 177, 0 },
380 { 255, 151, 0 },
381 { 255, 126, 0 },
382 { 255, 101, 0 },
383 { 255, 76, 0 },
384 { 255, 51, 0 },
385 { 255, 25, 0 },
386 { 255, 0, 0 }
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 );
392 else {
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],
397 gradient[i][2] ) );
398 meterBar.setPixel( i, 0, i+1 );
402 QString titleText;
403 QString confidenceString;
404 if ( spamError == noError )
406 if ( confidence >= 0 )
408 confidenceString = QString::number( confidence ) + "% &nbsp;";
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() + "&nbsp;";
416 titleText = i18n("%1% probability of being spam.\n\n"
417 "Full report:\nProbability=%2",
418 QString::number(percent,'f',2), filterHeader);
421 else
423 QString errorMsg;
424 switch ( spamError )
426 case errorExtractingAgentString:
427 errorMsg = i18n( "No Spam agent" );
428 break;
429 case couldNotConverScoreToFloat:
430 errorMsg = i18n( "Spam filter score not a number" );
431 break;
432 case couldNotConvertThresholdToFloatOrThresholdIsNegative:
433 errorMsg = i18n( "Threshold not a valid number" );
434 break;
435 case couldNotFindTheScoreField:
436 errorMsg = i18n( "Spam filter score could not be extracted from header" );
437 break;
438 case couldNotFindTheThresholdField:
439 errorMsg = i18n( "Threshold could not be extracted from header" );
440 break;
441 default:
442 errorMsg = i18n( "Error evaluating spam score" );
443 break;
445 // report the error in the spam filter
446 titleText = i18n("%1.\n\n"
447 "Full report:\n%2",
448 errorMsg, filterHeader );
450 return QString("<img src=\"%1\" width=\"%2\" height=\"%3\" style=\"border: 1px solid black;\" title=\"%4\"> &nbsp;")
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();
459 if ( !strategy )
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
473 // direction.
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.
482 QString spamHTML;
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() );
492 QString userHTML;
494 QString photoURL;
495 int photoWidth = 60;
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;
528 photoHeight = 60;
529 photoWidth = (int)( 60 / ratio );
530 photo = photo.scaled( photoWidth, photoHeight, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
532 photoURL = imgToDataUrl( photo );
535 else
537 photoURL = photoMemento->photo().url();
538 if ( photoURL.startsWith('/') )
539 photoURL.prepend( "file:" );
541 } else {
542 // if the memento is not finished yet, use other photo sources instead
543 useOtherPhotoSources = true;
545 } else {
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 );
562 QImage faceimage;
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 );
567 photoWidth = 48;
568 photoHeight = 48;
569 } else {
570 kDebug() << "Face: header image is" << faceimage.width() << "by"
571 << faceimage.height() << "not 48x48 Pixels";
573 } else {
574 kDebug() << "Failed to load decoded png from Face: header";
576 } else {
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 ) );
590 photoWidth = 48;
591 photoHeight = 48;
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")
611 .arg(subjectDir)
612 .arg( subjectString( message, flags ) );
614 headerStr += "<table class=\"outer\"><tr><td width=\"100%\"><table>\n";
615 //headerStr += "<table>\n";
616 // from line
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"
637 "<td>")
638 .arg(i18n("From: "))
639 + StringUtil::emailAddrAsAnchor( message->from(), StringUtil::DisplayFullAddress )
640 + ( message->headerByType( "Resent-From" ) ? "&nbsp;"
641 + i18n( "(resent from %1)",
642 StringUtil::emailAddrAsAnchor(
643 resentFrom, StringUtil::DisplayFullAddress ) )
644 : QString("") )
645 + ( !vCardName().isEmpty() ? "&nbsp;&nbsp;<a href=\"" + vCardName() + "\">"
646 + i18n("[vCard]") + "</a>"
647 : QString("") )
648 + ( !message->headerByType("Organization")
649 ? QString("")
650 : "&nbsp;&nbsp;("
651 + strToHtml(message->headerByType("Organization")->asUnicodeString())
652 + ')')
653 + "</td></tr>\n";
655 // to line
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,
662 "FullToAddressList",
663 GlobalSettings::self()->numberOfAddressesToShow() ) ) );
665 // cc line, if an
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,
672 "FullCcAddressList",
673 GlobalSettings::self()->numberOfAddressesToShow() ) ) );
675 // Bcc line, if any
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")
685 .arg(i18n("Date: "))
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>" ) );
708 headerStr.append(
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>&nbsp;<span style=\"padding-left: 20px;\">%3</span></div>\n")
713 .arg( subjectDir, i18n("Spam Status:"), spamHTML ) );
715 headerStr += "</div>\n\n";
716 return headerStr;
719 QString FancyHeaderStyle::imgToDataUrl( const QImage &image )
721 QByteArray ba;
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;
733 protected:
734 EnterpriseHeaderStyle() : HeaderStyle() {}
735 virtual ~EnterpriseHeaderStyle() {}
737 public:
738 const char * name() const { return "enterprise"; }
739 HeaderStyle * next() const {
740 #if defined KDEPIM_MOBILE_UI
741 return mobile();
742 #else
743 return brief();
744 #endif
746 HeaderStyle * prev() const { return fancy(); }
748 QString format( KMime::Message *message ) const;
750 virtual bool hasAttachmentQuickList() const {
751 return true;
755 QString EnterpriseHeaderStyle::format( KMime::Message *message ) const
757 if ( !message ) return QString();
758 const HeaderStrategy *strategy = headerStrategy();
759 if ( !strategy ) {
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 ) ?
767 "rtl" : "ltr" ;
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
773 // direction.
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
784 if( !isTopLevel() ){
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 " );
794 QString headerStr;
796 // 3D borders
797 if(isTopLevel())
798 headerStr +=
799 "<div style=\"position: fixed; top: 0px; left: 0px; background-color: #606060; "
800 "width: 10px; min-height: 100%;\">&nbsp;</div>"
801 "<div style=\"position: fixed; top: 0px; right: 0px; background-color: #606060; "
802 "width: 10px; min-height: 100%;\">&nbsp;</div>";
804 headerStr +=
805 "<div style=\"margin-left: 10px; top: 0px;\"><span style=\"font-size: 10px; font-weight: bold;\">"
806 + dateString( message, isPrinting(), /* shortDate */ false ) + "</span></div>"
807 // #0057ae
808 "<table style=\"background: "+activeColorDark.name()+"; border-collapse:collapse; top: 14px; min-width: 200px; \" cellpadding=0> \n"
809 " <tr> \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"
813 " <tr> \n"
814 " <td style=\"min-width: 6px; max-width: 6px; background: url("+imgpath+"left.png); \"></td> \n"
815 " <td style=\"\"> \n";
816 headerStr +=
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"
819 "</div>\n";
820 headerStr +=
821 " <table style=\"color: "+fontColor.name()+" ! important; margin: 1px; border-spacing: 0px;\" cellpadding=0> \n";
823 // subject
824 if ( strategy->showHeader( "subject" ) ) {
825 headerStr +=
826 " <tr> \n"
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"
830 " </tr> \n";
833 // from
834 if ( strategy->showHeader( "from" ) ) {
835 // TODO vcard
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 += "&nbsp;&nbsp;<a href=\"" + vCardName() + "\" "+linkColor+">" + i18n("[vCard]") + "</a>";
841 //TDDO strategy date
842 //if ( strategy->showHeader( "date" ) )
843 headerStr +=
844 " <tr> \n"
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> "
847 " </tr> ";
850 // to line
851 if ( strategy->showHeader( "to" ) ) {
852 headerStr +=
853 " <tr> "
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 ) +
857 " </td> "
858 " </tr>\n";
861 // cc line, if any
862 if ( strategy->showHeader( "cc" ) && message->cc( false ) ) {
863 headerStr +=
864 " <tr> "
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 ) +
868 " </td> "
869 " </tr>\n";
872 // bcc line, if any
873 if ( strategy->showHeader( "bcc" ) && message->bcc( false ) ) {
874 headerStr +=
875 " <tr> "
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 ) +
879 " </td> "
880 " </tr>\n";
883 // header-bottom
884 headerStr +=
885 " </table> \n"
886 " </td> \n"
887 " <td style=\"min-width: 6px; max-height: 15px; background: url("+imgpath+"right.png); \"></td> \n"
888 " </tr> \n"
889 " <tr> \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"
895 " </tr> \n"
896 " </table> \n";
898 // kmail icon
899 if( isTopLevel() ) {
901 // attachments
902 headerStr +=
903 "<div class=\"noprint\" style=\"position: absolute; top: 60px; right: 20px;\">"
904 "<div id=\"attachmentInjectionPoint\"></div>"
905 "</div>\n";
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;\">";
912 } else {
913 headerStr += "<div style=\"padding: 6px;\">";
916 // TODO
917 // spam status
918 // ### iterate over the rest of strategy->headerToDisplay() (or
919 // ### all headers if DefaultPolicy == Display) (elsewhere, too)
920 return headerStr;
923 // #####################
925 class MobileHeaderStyle : public HeaderStyle {
926 friend class HeaderStyle;
927 protected:
928 MobileHeaderStyle() : HeaderStyle() {}
929 virtual ~MobileHeaderStyle() {}
931 public:
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;
941 protected:
942 MobileExtendedHeaderStyle() : HeaderStyle() {}
943 virtual ~MobileExtendedHeaderStyle() {}
945 public:
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;
956 while ( true ) {
957 if ( pixelSize <= 8 )
958 break;
960 QFont font;
961 font.setPixelSize( pixelSize );
962 QFontMetrics fm( font );
963 if ( fm.width( text ) <= maximumWidth )
964 break;
966 pixelSize--;
969 return pixelSize;
972 static QString formatMobileHeader( KMime::Message *message, bool extendedFormat, const HeaderStyle *style )
974 if ( !message )
975 return QString();
977 // From
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 += "&nbsp;&nbsp;<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 );
987 // Background image
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();
993 QString flagsPart;
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\"/>";
1010 QString headerStr;
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";
1052 return headerStr;
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 ),
1073 mSourceObject( 0 )
1077 HeaderStyle::~HeaderStyle() {
1081 HeaderStyle * HeaderStyle::create( Type type ) {
1082 switch ( 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:
1103 return fancy();
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() {
1114 if ( !briefStyle )
1115 briefStyle = new BriefHeaderStyle();
1116 return briefStyle;
1119 HeaderStyle * HeaderStyle::plain() {
1120 if ( !plainStyle )
1121 plainStyle = new PlainHeaderStyle();
1122 return plainStyle;
1125 HeaderStyle * HeaderStyle::fancy() {
1126 if ( !fancyStyle )
1127 fancyStyle = new FancyHeaderStyle();
1128 return fancyStyle;
1131 HeaderStyle * HeaderStyle::enterprise() {
1132 if ( !enterpriseStyle )
1133 enterpriseStyle = new EnterpriseHeaderStyle();
1134 return enterpriseStyle;
1137 HeaderStyle * HeaderStyle::mobile() {
1138 if ( !mobileStyle )
1139 mobileStyle = new MobileHeaderStyle();
1140 return mobileStyle;
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 );