kmail

headerstyle.cpp
1 /* -*- c++ -*-
2  headerstyle.cpp
3 
4  This file is part of KMail, the KDE mail client.
5  Copyright (c) 2003 Marc Mutz <mutz@kde.org>
6 
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.
10 
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.
15 
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
19 
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 TQt library by Trolltech AS, Norway (or with modified versions
23  of TQt that use the same license as TQt), 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  TQt. 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.
30 */
31 
32 #ifdef HAVE_CONFIG_H
33 #include <config.h>
34 #endif
35 
36 #include "headerstyle.h"
37 
38 #include "headerstrategy.h"
39 #include "kmkernel.h"
40 #include "linklocator.h"
41 #include "kmmessage.h"
42 #include "spamheaderanalyzer.h"
43 #include "globalsettings.h"
44 
45 #include <libemailfunctions/email.h>
46 #include <libkdepim/kxface.h>
47 using namespace KPIM;
48 
49 #include <mimelib/string.h>
50 #include <mimelib/field.h>
51 #include <mimelib/headers.h>
52 
53 #include <kdebug.h>
54 #include <klocale.h>
55 #include <kglobal.h>
56 #include <kimproxy.h>
57 #include <kabc/stdaddressbook.h>
58 #include <kabc/addresseelist.h>
59 #include <kmdcodec.h>
60 #include <tqdatetime.h>
61 #include <tqbuffer.h>
62 #include <tqbitmap.h>
63 #include <tqimage.h>
64 #include <tqapplication.h>
65 #include <tqregexp.h>
66 
67 #include <kstandarddirs.h>
68 
69 namespace KMail {
70 
71  //
72  // Convenience functions:
73  //
74  static inline TQString directionOf( const TQString & str ) {
75  return str.isRightToLeft() ? "rtl" : "ltr" ;
76  }
77 
78 #if 0
79  // Converts to html. Changes URLs into href's, escapes HTML special
80  // chars and inserts the result into an <div> or <span> tag with
81  // "dir" set to "rtl" or "ltr" depending on the direction of @p str.
82  static TQString convertToHtmlBlock( const TQString & str, bool useSpan=false ) {
83  TQString dir = directionOf( str );
84  TQString format = "<%1 dir=\"%3\">%4</%2>";
85  return format.arg( useSpan ? "span" : "div" )
86  .arg( useSpan ? "span" : "div" )
87  .arg( dir )
88  .arg( LinkLocator::convertToHtml( str ) );
89  }
90 #endif
91 
92  // ### tmp wrapper to make kmreaderwin code working:
93  static TQString strToHtml( const TQString & str,
94  int flags = LinkLocator::PreserveSpaces ) {
95  return LinkLocator::convertToHtml( str, flags );
96  }
97 
98  //
99  // BriefHeaderStyle
100  // Show everything in a single line, don't show header field names.
101  //
102 
103  class BriefHeaderStyle : public HeaderStyle {
104  friend class ::KMail::HeaderStyle;
105  protected:
106  BriefHeaderStyle() : HeaderStyle() {}
107  virtual ~BriefHeaderStyle() {}
108 
109  public:
110  const char * name() const { return "brief"; }
111  const HeaderStyle * next() const { return plain(); }
112  const HeaderStyle * prev() const { return fancy(); }
113 
114  TQString format( const KMMessage * message, const HeaderStrategy * strategy,
115  const TQString & vCardName, bool printing, bool topLevel ) const;
116  };
117 
118  TQString BriefHeaderStyle::format( const KMMessage * message,
119  const HeaderStrategy * strategy,
120  const TQString & vCardName, bool printing, bool topLevel ) const {
121  Q_UNUSED( topLevel );
122  if ( !message ) return TQString();
123  if ( !strategy )
124  strategy = HeaderStrategy::brief();
125 
126  // The direction of the header is determined according to the direction
127  // of the application layout.
128 
129  TQString dir = TQApplication::reverseLayout() ? "rtl" : "ltr" ;
130 
131  // However, the direction of the message subject within the header is
132  // determined according to the contents of the subject itself. Since
133  // the "Re:" and "Fwd:" prefixes would always cause the subject to be
134  // considered left-to-right, they are ignored when determining its
135  // direction.
136 
137  TQString subjectDir;
138  if (!message->subject().isEmpty())
139  subjectDir = directionOf( message->cleanSubject() );
140  else
141  subjectDir = directionOf( i18n("No Subject") );
142 
143  // Prepare the date string (when printing always use the localized date)
144  TQString dateString;
145  if( printing ) {
146  TQDateTime dateTime;
147  KLocale * locale = KGlobal::locale();
148  dateTime.setTime_t( message->date() );
149  dateString = locale->formatDateTime( dateTime );
150  } else {
151  dateString = message->dateStr();
152  }
153 
154  TQString headerStr = "<div class=\"header\" dir=\"" + dir + "\">\n";
155 
156  if ( strategy->showHeader( "subject" ) )
157  headerStr += "<div dir=\"" + subjectDir + "\">\n"
158  "<b style=\"font-size:130%\">" +
159  strToHtml( message->subject() ) +
160  "</b></div>\n";
161 
162  TQStringList headerParts;
163 
164  if ( strategy->showHeader( "from" ) ) {
165  TQString fromStr = message->from();
166  if ( fromStr.isEmpty() ) // no valid email in from, maybe just a name
167  fromStr = message->fromStrip(); // let's use that
168  TQString fromPart = KMMessage::emailAddrAsAnchor( fromStr, true );
169  if ( !vCardName.isEmpty() )
170  fromPart += "&nbsp;&nbsp;<a href=\"" + vCardName + "\">" + i18n("[vCard]") + "</a>";
171  headerParts << fromPart;
172  }
173 
174  if ( strategy->showHeader( "cc" ) && !message->cc().isEmpty() )
175  headerParts << i18n("CC: ") + KMMessage::emailAddrAsAnchor( message->cc(), true );
176 
177  if ( strategy->showHeader( "bcc" ) && !message->bcc().isEmpty() )
178  headerParts << i18n("BCC: ") + KMMessage::emailAddrAsAnchor( message->bcc(), true );
179 
180  if ( strategy->showHeader( "date" ) )
181  headerParts << strToHtml(message->dateShortStr());
182 
183  // remove all empty (modulo whitespace) entries and joins them via ", \n"
184  headerStr += " (" + headerParts.grep( TQRegExp( "\\S" ) ).join( ",\n" ) + ')';
185 
186  headerStr += "</div>\n";
187 
188  // ### iterate over the rest of strategy->headerToDisplay() (or
189  // ### all headers if DefaultPolicy == Display) (elsewhere, too)
190  return headerStr;
191  }
192 
193  //
194  // PlainHeaderStyle:
195  // show every header field on a line by itself,
196  // show subject larger
197  //
198 
199  class PlainHeaderStyle : public HeaderStyle {
200  friend class ::KMail::HeaderStyle;
201  protected:
202  PlainHeaderStyle() : HeaderStyle() {}
203  virtual ~PlainHeaderStyle() {}
204 
205  public:
206  const char * name() const { return "plain"; }
207  const HeaderStyle * next() const { return fancy(); }
208  const HeaderStyle * prev() const { return brief(); }
209 
210  TQString format( const KMMessage * message, const HeaderStrategy * strategy,
211  const TQString & vCardName, bool printing, bool topLevel ) const;
212 
213  private:
214  TQString formatAllMessageHeaders( const KMMessage * message ) const;
215  };
216 
217  TQString PlainHeaderStyle::format( const KMMessage * message,
218  const HeaderStrategy * strategy,
219  const TQString & vCardName, bool printing, bool topLevel ) const {
220  Q_UNUSED( topLevel );
221  if ( !message ) return TQString();
222  if ( !strategy )
223  strategy = HeaderStrategy::rich();
224 
225  // The direction of the header is determined according to the direction
226  // of the application layout.
227 
228  TQString dir = ( TQApplication::reverseLayout() ? "rtl" : "ltr" );
229 
230  // However, the direction of the message subject within the header is
231  // determined according to the contents of the subject itself. Since
232  // the "Re:" and "Fwd:" prefixes would always cause the subject to be
233  // considered left-to-right, they are ignored when determining its
234  // direction.
235 
236  TQString subjectDir;
237  if (!message->subject().isEmpty())
238  subjectDir = directionOf( message->cleanSubject() );
239  else
240  subjectDir = directionOf( i18n("No Subject") );
241 
242  // Prepare the date string (when printing always use the localized date)
243  TQString dateString;
244  if( printing ) {
245  TQDateTime dateTime;
246  KLocale* locale = KGlobal::locale();
247  dateTime.setTime_t( message->date() );
248  dateString = locale->formatDateTime( dateTime );
249  }
250  else {
251  dateString = message->dateStr();
252  }
253 
254  TQString headerStr;
255 
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= TQString("<div class=\"header\" dir=\"ltr\">");
261  headerStr += formatAllMessageHeaders( message );
262  return headerStr + "</div>";
263  }
264 
265  headerStr = TQString("<div class=\"header\" dir=\"%1\">").arg(dir);
266 
267  //case HdrLong:
268  if ( strategy->showHeader( "subject" ) )
269  headerStr += TQString("<div dir=\"%1\"><b style=\"font-size:130%\">" +
270  strToHtml(message->subject()) + "</b></div>\n")
271  .arg(subjectDir);
272 
273  if ( strategy->showHeader( "date" ) )
274  headerStr.append(i18n("Date: ") + strToHtml(dateString)+"<br>\n");
275 
276 #if 0
277  // Get Instant Messaging presence
278  TQString presence;
279  TQString kabcUid;
280  if ( strategy->showHeader( "status" ) )
281  {
282  KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
283  KABC::AddresseeList addresses = addressBook->findByEmail( KPIM::getFirstEmailAddress( message->from() ) );
284  ::KIMProxy *imProxy = KMKernel::self()->imProxy();
285  kabcUid = addresses[0].uid();
286  presence = imProxy->presenceString( kabcUid );
287  }
288 #endif
289 
290  if ( strategy->showHeader( "from" ) ) {
291  TQString fromStr = message->from();
292  if ( fromStr.isEmpty() ) // no valid email in from, maybe just a name
293  fromStr = message->fromStrip(); // let's use that
294  headerStr.append(i18n("From: ") +
295  KMMessage::emailAddrAsAnchor( fromStr, false, "", true ) );
296  if ( !vCardName.isEmpty() )
297  headerStr.append("&nbsp;&nbsp;<a href=\"" + vCardName +
298  "\">" + i18n("[vCard]") + "</a>" );
299 #if 0
300  if ( !presence.isEmpty() && strategy->showHeader( "status" ) )
301  headerStr.append("&nbsp;&nbsp;(<span name=\"presence-" + kabcUid + "\">" + presence + "</span>)" );
302 #endif
303 
304  if ( strategy->showHeader( "organization" )
305  && !message->headerField("Organization").isEmpty())
306  headerStr.append("&nbsp;&nbsp;(" +
307  strToHtml(message->headerField("Organization")) + ")");
308  headerStr.append("<br>\n");
309  }
310 
311  if ( strategy->showHeader( "to" ) )
312  headerStr.append(i18n("To: ")+
313  KMMessage::emailAddrAsAnchor(message->to(),false) + "<br>\n");
314 
315  if ( strategy->showHeader( "cc" ) && !message->cc().isEmpty() )
316  headerStr.append(i18n("CC: ")+
317  KMMessage::emailAddrAsAnchor(message->cc(),false) + "<br>\n");
318 
319  if ( strategy->showHeader( "bcc" ) && !message->bcc().isEmpty() )
320  headerStr.append(i18n("BCC: ")+
321  KMMessage::emailAddrAsAnchor(message->bcc(),false) + "<br>\n");
322 
323  if ( strategy->showHeader( "reply-to" ) && !message->replyTo().isEmpty())
324  headerStr.append(i18n("Reply to: ")+
325  KMMessage::emailAddrAsAnchor(message->replyTo(),false) + "<br>\n");
326 
327  headerStr += "</div>\n";
328 
329  return headerStr;
330  }
331 
332  TQString PlainHeaderStyle::formatAllMessageHeaders( const KMMessage * message ) const {
333  const DwHeaders & headers = message->headers();
334  TQString result;
335 
336  for ( const DwField * field = headers.FirstField() ; field ; field = field->Next() ) {
337  result += ( field->FieldNameStr() + ": " ).c_str();
338  result += strToHtml( field->FieldBodyStr().c_str() );
339  result += "<br>\n";
340  }
341 
342  return result;
343  }
344 
345  //
346  // FancyHeaderStyle:
347  // Like PlainHeaderStyle, but with slick frames and background colours.
348  //
349 
350  class FancyHeaderStyle : public HeaderStyle {
351  friend class ::KMail::HeaderStyle;
352  protected:
353  FancyHeaderStyle() : HeaderStyle() {}
354  virtual ~FancyHeaderStyle() {}
355 
356  public:
357  const char * name() const { return "fancy"; }
358  const HeaderStyle * next() const { return enterprise(); }
359  const HeaderStyle * prev() const { return plain(); }
360 
361  TQString format( const KMMessage * message, const HeaderStrategy * strategy,
362  const TQString & vCardName, bool printing, bool topLevel ) const;
363  static TQString imgToDataUrl( const TQImage & image,
364  const char *fmt = "PNG" );
365 
366  private:
367  static TQString drawSpamMeter( double percent, const TQString & filterHeader );
368 
369  };
370 
371  TQString FancyHeaderStyle::drawSpamMeter( double percent,
372  const TQString & filterHeader )
373  {
374  TQImage meterBar( 20, 1, 8, 24 );
375  const unsigned short gradient[20][3] = {
376  { 0, 255, 0 },
377  { 27, 254, 0 },
378  { 54, 252, 0 },
379  { 80, 250, 0 },
380  { 107, 249, 0 },
381  { 135, 247, 0 },
382  { 161, 246, 0 },
383  { 187, 244, 0 },
384  { 214, 242, 0 },
385  { 241, 241, 0 },
386  { 255, 228, 0 },
387  { 255, 202, 0 },
388  { 255, 177, 0 },
389  { 255, 151, 0 },
390  { 255, 126, 0 },
391  { 255, 101, 0 },
392  { 255, 76, 0 },
393  { 255, 51, 0 },
394  { 255, 25, 0 },
395  { 255, 0, 0 }
396  };
397  meterBar.setColor( 21, tqRgb( 255, 255, 255 ) );
398  meterBar.setColor( 22, tqRgb( 170, 170, 170 ) );
399  if ( percent < 0 ) // grey is for errors
400  meterBar.fill( 22 );
401  else {
402  meterBar.fill( 21 );
403  int max = TQMIN( 20, static_cast<int>( percent ) / 5 );
404  for ( int i = 0; i < max; ++i ) {
405  meterBar.setColor( i+1, tqRgb( gradient[i][0], gradient[i][1],
406  gradient[i][2] ) );
407  meterBar.setPixel( i, 0, i+1 );
408  }
409  }
410  TQString titleText = i18n("%1% probability of being spam.\n\nFull report:\n%2")
411  .arg( TQString::number( percent ), filterHeader );
412  return TQString("<img src=\"%1\" width=\"%2\" height=\"%3\" style=\"border: 1px solid black;\" title=\"%4\"> &nbsp;")
413  .arg( imgToDataUrl( meterBar, "PPM" ), TQString::number( 20 ),
414  TQString::number( 5 ), titleText );
415  }
416 
417 
418  TQString FancyHeaderStyle::format( const KMMessage * message,
419  const HeaderStrategy * strategy,
420  const TQString & vCardName, bool printing, bool topLevel ) const {
421  Q_UNUSED( topLevel );
422  if ( !message ) return TQString();
423  if ( !strategy )
424  strategy = HeaderStrategy::rich();
425 
426  KConfigGroup configReader( KMKernel::config(), "Reader" );
427 
428  // ### from kmreaderwin begin
429  // The direction of the header is determined according to the direction
430  // of the application layout.
431 
432  TQString dir = ( TQApplication::reverseLayout() ? "rtl" : "ltr" );
433  TQString headerStr = TQString("<div class=\"fancy header\" dir=\"%1\">\n").arg(dir);
434 
435  // However, the direction of the message subject within the header is
436  // determined according to the contents of the subject itself. Since
437  // the "Re:" and "Fwd:" prefixes would always cause the subject to be
438  // considered left-to-right, they are ignored when determining its
439  // direction.
440 
441  TQString subjectDir;
442  if ( !message->subject().isEmpty() )
443  subjectDir = directionOf( message->cleanSubject() );
444  else
445  subjectDir = directionOf( i18n("No Subject") );
446 
447  // Prepare the date string (when printing always use the localized date)
448  TQString dateString;
449  if( printing ) {
450  TQDateTime dateTime;
451  KLocale* locale = KGlobal::locale();
452  dateTime.setTime_t( message->date() );
453  dateString = locale->formatDateTime( dateTime );
454  }
455  else {
456  dateString = message->dateStr();
457  }
458 
459  // Spam header display.
460  // If the spamSpamStatus config value is true then we look for headers
461  // from a few spam filters and try to create visually meaningful graphics
462  // out of the spam scores.
463 
464  TQString spamHTML;
465 
466  if ( configReader.readBoolEntry( "showSpamStatus", true ) ) {
467  SpamScores scores = SpamHeaderAnalyzer::getSpamScores( message );
468  for ( SpamScoresIterator it = scores.begin(); it != scores.end(); ++it )
469  spamHTML += (*it).agent() + " " +
470  drawSpamMeter( (*it).score(), (*it).spamHeader() );
471  }
472 
473  TQString userHTML;
474  TQString presence;
475 
476  // IM presence and kabc photo
477 
478  ::KIMProxy *imProxy = KMKernel::self()->imProxy();
479  TQString kabcUid;
480  KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
481  KABC::AddresseeList addresses = addressBook->findByEmail( KPIM::getFirstEmailAddress( message->from() ) );
482 
483  TQString photoURL;
484  int photoWidth = 60;
485  int photoHeight = 60;
486  if( addresses.count() == 1 )
487  {
488  // kabcUid is embedded in im: URIs to indicate which IM contact to message
489  kabcUid = addresses[0].uid();
490 
491  if ( imProxy->initialize() ) {
492  // im status
493  presence = imProxy->presenceString( kabcUid );
494  if ( !presence.isEmpty() )
495  {
496  TQString presenceIcon = TQString::fromLatin1( " <img src=\"%1\"/>" )
497  .arg( imgToDataUrl( imProxy->presenceIcon( kabcUid ).convertToImage() ) );
498  presence += presenceIcon;
499  }
500  }
501  // picture
502  if ( addresses[0].photo().isIntern() )
503  {
504  // get photo data and convert to data: url
505  //kdDebug( 5006 ) << "INTERNAL photo found" << endl;
506  TQImage photo = addresses[0].photo().data();
507  if ( !photo.isNull() )
508  {
509  photoWidth = photo.width();
510  photoHeight = photo.height();
511  // scale below 60, otherwise it can get way too large
512  if ( photoHeight > 60 ) {
513  double ratio = ( double )photoHeight / ( double )photoWidth;
514  photoHeight = 60;
515  photoWidth = (int)( 60 / ratio );
516  photo = photo.smoothScale( photoWidth, photoHeight );
517  }
518  photoURL = imgToDataUrl( photo );
519  }
520  }
521  else
522  {
523  //kdDebug( 5006 ) << "URL found" << endl;
524  photoURL = addresses[0].photo().url();
525  if ( photoURL.startsWith("/") )
526  photoURL.prepend( "file:" );
527  }
528  }
529  else // TODO: find a usable one
530  {
531  kdDebug( 5006 ) << "Multiple / No addressees matched email address; Count is " << addresses.count() << endl;
532  userHTML = "&nbsp;";
533  }
534 
535  if( photoURL.isEmpty() ) {
536  // no photo, look for a Face header
537  TQString faceheader = message->headerField( "Face" );
538  if ( !faceheader.isEmpty() ) {
539  TQImage faceimage;
540 
541  kdDebug( 5006 ) << "Found Face: header" << endl;
542 
543  TQCString facestring = faceheader.utf8();
544  // Spec says header should be less than 998 bytes
545  // Face: is 5 characters
546  if ( facestring.length() < 993 ) {
547  TQByteArray facearray;
548  KCodecs::base64Decode(facestring, facearray);
549 
550  TQImage faceimage;
551  if ( faceimage.loadFromData( facearray, "png" ) ) {
552  // Spec says image must be 48x48 pixels
553  if ( (48 == faceimage.width()) && (48 == faceimage.height()) ) {
554  photoURL = imgToDataUrl( faceimage );
555  photoWidth = 48;
556  photoHeight = 48;
557  } else {
558  kdDebug( 5006 ) << "Face: header image is" << faceimage.width() << "by" << faceimage.height() <<"not 48x48 Pixels" << endl;
559  }
560  } else {
561  kdDebug( 5006 ) << "Failed to load decoded png from Face: header" << endl;
562  }
563  } else {
564  kdDebug( 5006 ) << "Face: header too long at " << facestring.length() << endl;
565  }
566  }
567  }
568 
569  if( photoURL.isEmpty() )
570  {
571  // no photo, look for a X-Face header
572  TQString xfaceURL;
573  TQString xfhead = message->headerField( "X-Face" );
574  if ( !xfhead.isEmpty() )
575  {
576  KXFace xf;
577  photoURL = imgToDataUrl( xf.toImage( xfhead ) );
578  photoWidth = 48;
579  photoHeight = 48;
580 
581  }
582  }
583 
584  if( !photoURL.isEmpty() )
585  {
586  //kdDebug( 5006 ) << "Got a photo: " << photoURL << endl;
587  userHTML = TQString("<img src=\"%1\" width=\"%2\" height=\"%3\">")
588  .arg( photoURL ).arg( photoWidth ).arg( photoHeight );
589  if ( presence.isEmpty() ) {
590  userHTML = TQString("<div class=\"senderpic\">") + userHTML + "</div>";
591  } else {
592  userHTML = TQString( "<div class=\"senderpic\">"
593  "<a href=\"im:%1\">%2<div class=\"senderstatus\">"
594  "<span name=\"presence-%3\">%4</span></div></a>"
595  "</div>" ).arg( kabcUid )
596  .arg( userHTML )
597  .arg( kabcUid )
598  .arg( presence );
599  }
600  } else {
601  // we don't have a photo, just show presence, if we have it
602  if ( !presence.isEmpty() )
603  userHTML = TQString( "<a href=\"im:%1\"><div class=\"senderstatus\">"
604  "<span name=\"presence-%2\">%3</span></div></a>" )
605  .arg( kabcUid )
606  .arg( kabcUid )
607  .arg( presence );
608  }
609 #if 0
610  // Disabled 'Launch IM' link in headers - Will
611  if ( imProxy->imAppsAvailable() )
612  presence = "<a name=\"launchim\" href=\"kmail:startIMApp\">" + i18n("Launch IM") + "</a></span>";
613  // do nothing - no im apps available, leave presence empty
614  //presence = i18n( "DCOP/InstantMessenger not installed" );
615  kdDebug( 5006 ) << "final presence: '" << presence << "'" << endl;
616 #endif
617 
618  TQString timeHTML;
619  if ( GlobalSettings::self()->showCurrentTime() && strategy->showHeader( "date" ) ) {
620  DwHeaders& header = message->headers();
621  if ( header.HasDate() ) {
622  DwDateTime& origDate = header.Date();
623  int zone = origDate.Zone();
624  // kdDebug() << "FancyHeaderStyle::format() zone offset (in minutes): " << zone << endl;
625 
626  // copyed fro mimelib -- code to determine local timezone
627  time_t t_now = time((time_t*) 0);
628 #if defined(HAVE_GMTIME_R)
629  struct tm utc;
630  gmtime_r(&t_now, &utc);
631  struct tm local;
632  localtime_r(&t_now, &local);
633 #else
634  struct tm utc = *gmtime(&t_now);
635  struct tm local = *localtime(&t_now);
636 #endif
637  DwUint32 t_local = 0;
638  t_local = 24 * t_local + local.tm_hour;
639  t_local = 60 * t_local + local.tm_min;
640  t_local = 60 * t_local + local.tm_sec;
641  DwUint32 t_utc = 0;
642  t_utc = 24 * t_utc + utc.tm_hour;
643  t_utc = 60 * t_utc + utc.tm_min;
644  t_utc = 60 * t_utc + utc.tm_sec;
645  int lzone = (int) (t_local - t_utc) / 60;
646 
647  // kdDebug() << "FancyHeaderStyle::format() local zone offset (in minutes): " << lzone << endl;
648 
649  TQTime currTime = TQTime::currentTime( Qt::UTC );
650 
651  // kdDebug() << "FancyHeaderStyle::format() current time: " << currTime << endl;
652 
653  // now currTime contain message sender local time
654  currTime = currTime.addSecs( zone * 60 );
655 
656  TQString timeofday;
657  TQString color;
658  TQString bg_color;
659  TQString bg_image;
660  if ( currTime > TQTime( 0, 0, 0 ) && currTime <= TQTime( 6, 0, 0 ) ) {
661  timeofday = i18n( "Night" );
662  color = "white";
663  bg_color = "#000B6B";
664  bg_image = "url(data:image/png;base64,"
665  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAyCAIAAAASmSbdAAAAS0lEQVQI11WOsRGAQAzDOG/LHoz9"
666  "kikIcF+kSBxbPs7LoNGVapAI0Zn+O+8NUwldozn6io7G7kdS/5zi7i+BvUM/5uSXlIfzMHx/bmWR"
667  "k++yj9rZAAAAAElFTkSuQmCC)";
668  }
669  else if ( currTime > TQTime( 6, 0, 0 ) && currTime <= TQTime( 12, 0, 0 ) ) {
670  timeofday = i18n( "Morning" );
671  color = "white";
672  bg_color = "#00A6FF";
673  bg_image = "url(data:image/png;base64,"
674  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAyCAYAAACd+7GKAAAAWklEQVQI122OQQ7DMAzDaP3/dfuO"
675  "pWSHJgva7iZIBk3m/Ew5hexCHVCilewzFHKEbFZqgxJQWyzKhWKl9unqddJj8+L9sl0oR2gUim+o"
676  "zu4uSh7kn67/DNv+C4tsZOtjAWEHAAAAAElFTkSuQmCC)";
677  }
678  else if ( currTime > TQTime( 12, 0, 0 ) && currTime <= TQTime( 18, 0, 0 ) ) {
679  timeofday = i18n( "Afternoon" );
680  color = "black";
681  bg_color = "#00A6FF";
682  bg_image = "url(data:image/png;base64,"
683  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAyCAYAAACd+7GKAAAAPUlEQVQI132OwQ0AIAwCSfcfw91c"
684  "QsCfRm399HFwoWjdDhMICQhxHSWMQPhkTCoqWRZU2h5i9tr4GZfmV5t3wWUI3h+NugAAAABJRU5E"
685  "rkJggg==)";
686  }
687  else {
688  timeofday = i18n( "Evening" );
689  color = "white";
690  bg_color = "#0014CC";
691  bg_image = "url(data:image/png;base64,"
692  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAyCAYAAACd+7GKAAAAWklEQVQI11WOyRHAMAgDNQuUlBrS"
693  "fyFpAfKwje0PwyEt0vN+hVsJpzS6QML2ziWcFI6mZBZNSVDXYehyUgI1XsLI9eimHDH6kW0ddVIO"
694  "xx7JjrtshlbXlLDSD+WhJ+hwqWo8AAAAAElFTkSuQmCC)";
695  }
696 
697  TQString tformat;
698  if ( KGlobal::locale()->use12Clock() ) {
699  tformat = "h:mm AP";
700  }
701  else {
702  tformat = "h:mm";
703  }
704 
705  // kdDebug() << "FancyHeaderStyle::format() current time: " << currTime << " (" << timeofday << ")" << endl;
706 
707  timeHTML.append( TQString(
708  "<div style=\""
709  "border:1px solid %1;"
710  "color:%2;"
711  "background-image:%3;"
712  "background-position:center left;"
713  "background-repeat:repeat-x;"
714  "text-align:center;"
715  "font-size:12px;"
716  "padding:2px;"
717  "width:50px;"
718  "heigth:50px;"
719  "margin: 0px 0px 3px 0px;"
720  "\" class=\"curtime\">%4<br />%5<br />%6</div>"
721  )
722  .arg( bg_color )
723  .arg( color )
724  .arg( bg_image )
725  .arg( i18n( "Now:" ) )
726  .arg( currTime.toString( tformat ) )
727  .arg( timeofday )
728  );
729  }
730  else {
731  // kdDebug() << "FancyHeaderStyle::format() no date header to display" << endl;
732  }
733  }
734 
735  //case HdrFancy:
736  // the subject line and box below for details
737  if ( strategy->showHeader( "subject" ) ) {
738  const int flags = LinkLocator::PreserveSpaces |
739  ( GlobalSettings::self()->showEmoticons() ?
740  LinkLocator::ReplaceSmileys : 0 );
741  headerStr += TQString("<div dir=\"%1\">%2</div>\n")
742  .arg(subjectDir)
743  .arg(message->subject().isEmpty()?
744  i18n("No Subject") :
745  strToHtml( message->subject(), flags ));
746  }
747  headerStr += "<table class=\"outer\"><tr><td width=\"100%\"><table>\n";
748  //headerStr += "<table>\n";
749  // from line
750  // the mailto: URLs can contain %3 etc., therefore usage of multiple
751  // TQString::arg is not possible
752  if ( strategy->showHeader( "from" ) ) {
753  TQString fromStr = message->from();
754  if ( fromStr.isEmpty() ) // no valid email in from, maybe just a name
755  fromStr = message->fromStrip(); // let's use that
756  headerStr += TQString("<tr><th>%1</th>\n"
757  "<td>")
758  .arg(i18n("From: "))
759  + KMMessage::emailAddrAsAnchor( fromStr, false )
760  + ( !message->headerField( "Resent-From" ).isEmpty() ? "&nbsp;"
761  + i18n("(resent from %1)")
763  message->headerField( "Resent-From" ),false) )
764  : TQString("") )
765  + ( !vCardName.isEmpty() ? "&nbsp;&nbsp;<a href=\"" + vCardName + "\">"
766  + i18n("[vCard]") + "</a>"
767  : TQString("") )
768 #if 0
769  + ( ( !presence.isEmpty() )
770  ? "&nbsp;&nbsp;(<span name=\"presence-" + kabcUid + "\">" + presence + "</span>)"
771  : TQString("") )
772 #endif
773  + ( message->headerField("Organization").isEmpty()
774  ? TQString("")
775  : "&nbsp;&nbsp;("
776  + strToHtml(message->headerField("Organization"))
777  + ")")
778  + "</td></tr>\n";
779  }
780  // to line
781  if ( strategy->showHeader( "to" ) )
782  headerStr.append(TQString("<tr><th>%1</th>\n"
783  "<td>%2</td></tr>\n")
784  .arg(i18n("To: "))
785  .arg(KMMessage::emailAddrAsAnchor(message->to(),false)));
786 
787  // cc line, if any
788  if ( strategy->showHeader( "cc" ) && !message->cc().isEmpty())
789  headerStr.append(TQString("<tr><th>%1</th>\n"
790  "<td>%2</td></tr>\n")
791  .arg(i18n("CC: "))
792  .arg(KMMessage::emailAddrAsAnchor(message->cc(),false)));
793 
794  // Bcc line, if any
795  if ( strategy->showHeader( "bcc" ) && !message->bcc().isEmpty())
796  headerStr.append(TQString("<tr><th>%1</th>\n"
797  "<td>%2</td></tr>\n")
798  .arg(i18n("BCC: "))
799  .arg(KMMessage::emailAddrAsAnchor(message->bcc(),false)));
800 
801  if ( strategy->showHeader( "date" ) )
802  headerStr.append(TQString("<tr><th>%1</th>\n"
803  "<td dir=\"%2\">%3</td></tr>\n")
804  .arg(i18n("Date: "))
805  .arg( directionOf( message->dateStr() ) )
806  .arg(strToHtml(dateString)));
807 
808  if ( GlobalSettings::self()->showUserAgent() ) {
809  if ( strategy->showHeader( "user-agent" ) ) {
810  if ( !message->headerField("User-Agent").isEmpty() ) {
811  headerStr.append(TQString("<tr><th>%1</th>\n"
812  "<td>%2</td></tr>\n")
813  .arg(i18n("User-Agent: "))
814  .arg( strToHtml( message->headerField("User-Agent") ) ) );
815  }
816  }
817 
818  if ( strategy->showHeader( "x-mailer" ) ) {
819  if ( !message->headerField("X-Mailer").isEmpty() ) {
820  headerStr.append(TQString("<tr><th>%1</th>\n"
821  "<td>%2</td></tr>\n")
822  .arg(i18n("X-Mailer: "))
823  .arg( strToHtml( message->headerField("X-Mailer") ) ) );
824  }
825  }
826  }
827 
828  // FIXME: Show status in synthetic header style field. Decide whether this or current in brackets style is best and remove one.
829  /* if( strategy->showHeader( "status" ) )
830  headerStr.append( TQString( "<tr><th>%1</th>\n"
831  "<td dir=\"%2\">%3</td></tr>\n")
832  .arg(i18n("Sender status: "))
833  .arg( directionOf( onlineStatus ) )
834  .arg(onlineStatus));
835  */
836  headerStr.append( TQString("<tr><td colspan=\"2\"><div id=\"attachmentInjectionPoint\"></div></td></tr>" ) );
837  headerStr.append(
838  TQString( "</table></td><td align=\"center\" valign=\"top\">%1%2</td></tr></table>\n" )
839  .arg(timeHTML)
840  .arg(userHTML) );
841 
842  if ( !spamHTML.isEmpty() )
843  headerStr.append( TQString( "<div class=\"spamheader\" dir=\"%1\"><b>%2</b>&nbsp;<span style=\"padding-left: 20px;\">%3</span></div>\n")
844  .arg( subjectDir, i18n("Spam Status:"), spamHTML ) );
845 
846  headerStr += "</div>\n\n";
847  return headerStr;
848  }
849 
850  TQString FancyHeaderStyle::imgToDataUrl( const TQImage &image, const char* fmt )
851  {
852  TQByteArray ba;
853  TQBuffer buffer( ba );
854  buffer.open( IO_WriteOnly );
855  image.save( &buffer, fmt );
856  return TQString::fromLatin1("data:image/%1;base64,%2")
857  .arg( fmt, KCodecs::base64Encode( ba ).data() );
858  }
859 
860 // #####################
861 
862  class EnterpriseHeaderStyle : public HeaderStyle {
863  friend class ::KMail::HeaderStyle;
864  protected:
865  EnterpriseHeaderStyle() : HeaderStyle() {}
866  virtual ~EnterpriseHeaderStyle() {}
867 
868  public:
869  const char * name() const { return "enterprise"; }
870  const HeaderStyle * next() const { return brief(); }
871  const HeaderStyle * prev() const { return fancy(); }
872 
873  TQString format( const KMMessage * message, const HeaderStrategy * strategy,
874  const TQString & vCardName, bool printing, bool topLevel ) const;
875  };
876 
877  TQString EnterpriseHeaderStyle::format( const KMMessage * message,
878  const HeaderStrategy * strategy,
879  const TQString & vCardName, bool printing, bool topLevel ) const {
880  if ( !message ) return TQString();
881  if ( !strategy )
882  strategy = HeaderStrategy::brief();
883 
884  // The direction of the header is determined according to the direction
885  // of the application layout.
886 
887  TQString dir = TQApplication::reverseLayout() ? "rtl" : "ltr" ;
888 
889  // However, the direction of the message subject within the header is
890  // determined according to the contents of the subject itself. Since
891  // the "Re:" and "Fwd:" prefixes would always cause the subject to be
892  // considered left-to-right, they are ignored when determining its
893  // direction.
894 
895  TQString subjectDir;
896  if (!message->subject().isEmpty())
897  subjectDir = directionOf( message->cleanSubject() );
898  else
899  subjectDir = directionOf( i18n("No Subject") );
900 
901  // colors depend on if its encapsulated or not
902  TQColor fontColor(TQt::white);
903  TQString linkColor = "class =\"white\"";
904  const TQColor activeColor = tqApp->palette().active().highlight();
905  TQColor activeColorDark = activeColor.dark(130);
906  // reverse colors for encapsulated
907  if( !topLevel ){
908  activeColorDark = activeColor.dark(50);
909  fontColor = TQColor(TQt::black);
910  linkColor = "class =\"black\"";
911  }
912 
913  // Prepare the date string (when printing always use the localized date)
914  TQString dateString;
915  if( printing ) {
916  TQDateTime dateTime;
917  KLocale * locale = KGlobal::locale();
918  dateTime.setTime_t( message->date() );
919  dateString = locale->formatDateTime( dateTime );
920  } else {
921  dateString = message->dateStr();
922  }
923 
924  TQString imgpath(locate("data","kmail/pics/"));
925  imgpath.append("enterprise_");
926  const TQString borderSettings(" padding-top: 0px; padding-bottom: 0px; border-width: 0px ");
927  TQString headerStr ("");
928 
929  // 3D borders
930  if(topLevel)
931  headerStr +=
932  "<div style=\"position: fixed; top: 0px; left: 0px; background-color: #606060; "
933  "width: 10px; min-height: 100%;\">&nbsp;</div>"
934  "<div style=\"position: fixed; top: 0px; right: 0px; background-color: #606060; "
935  "width: 10px; min-height: 100%;\">&nbsp;</div>";
936 
937  headerStr += ""
938  "<div style=\"margin-left: 10px; top: 0px;\"><span style=\"font-size: 10px; font-weight: bold;\">"+dateString+"</span></div>"
939  // #0057ae
940  "<table style=\"background: "+activeColorDark.name()+"; border-collapse:collapse; top: 14px; min-width: 200px; \" cellpadding=0> \n"
941  " <tr> \n"
942  " <td style=\"min-width: 6px; background-image: url("+imgpath+"top_left.png); \"></td> \n"
943  " <td style=\"height: 6px; width: 100%; background: url("+imgpath+"top.png); \"></td> \n"
944  " <td style=\"min-width: 6px; background: url("+imgpath+"top_right.png); \"></td> </tr> \n"
945  " <tr> \n"
946  " <td style=\"min-width: 6px; max-width: 6px; background: url("+imgpath+"left.png); \"></td> \n"
947  " <td style=\"\"> \n";
948 
949  headerStr +=
950  " <div class=\"noprint\" style=\"z-index: 1; float:right; position: relative; top: -35px; right: 20px ;\">\n"
951  " <img src=\""+imgpath+"icon.png\">\n"
952  " </div>\n";
953 
954  headerStr +=
955  " <table style=\"color: "+fontColor.name()+" ! important; margin: 1px; border-spacing: 0px;\" cellpadding=0> \n";
956 
957  // subject
958  //strToHtml( message->subject() )
959  if ( strategy->showHeader( "subject" ) ){
960  headerStr +=
961  " <tr> \n"
962  " <td style=\"font-size: 6px; text-align: right; padding-left: 5px; padding-right: 24px; "+borderSettings+"\"></td> \n"
963  " <td style=\"font-weight: bolder; font-size: 120%; padding-right: 91px; "+borderSettings+"\">"+message->subject()+"</td> \n"
964  " </tr> \n";
965  }
966 
967  // from
968  if ( strategy->showHeader( "from" ) ){
969  TQString fromStr = message->from();
970  if ( fromStr.isEmpty() ) // no valid email in from, maybe just a name
971  fromStr = message->fromStrip(); // let's use that
972  // TODO vcard
973  TQString fromPart = KMMessage::emailAddrAsAnchor( fromStr, true, linkColor );
974  if ( !vCardName.isEmpty() )
975  fromPart += "&nbsp;&nbsp;<a href=\"" + vCardName + "\" "+linkColor+">" + i18n("[vCard]") + "</a>";
976  //TODO strategy date
977  //if ( strategy->showHeader( "date" ) )
978  headerStr +=
979  " <tr> \n"
980  " <td style=\"font-size: 6px; padding-left: 5px; padding-right: 24px; text-align: right; "+borderSettings+"\">"+i18n("From: ")+"</td> \n"
981  " <td style=\""+borderSettings+"\">"+ fromPart +"</td> "
982  " </tr> ";
983  }
984 
985  // to line
986  if( strategy->showHeader( "to" ) )
987  headerStr +=
988  " <tr> "
989  " <td style=\"font-size: 6px; text-align: right; padding-left: 5px; padding-right: 24px; " + borderSettings + "\">" + i18n("To: ") + "</td> "
990  " <td style=\"" + borderSettings + "\">" +
991  KMMessage::emailAddrAsAnchor( message->to(), false, linkColor ) +
992  " </td> "
993  " </tr>\n";
994 
995  // cc line, if any
996  if ( strategy->showHeader( "cc" ) && !message->cc().isEmpty() )
997  headerStr +=
998  " <tr> "
999  " <td style=\"font-size: 6px; text-align: right; padding-left: 5px; padding-right: 24px; " + borderSettings + "\">" + i18n("CC: ") + "</td> "
1000  " <td style=\"" + borderSettings + "\">" +
1001  KMMessage::emailAddrAsAnchor( message->cc(), false, linkColor ) +
1002  " </td> "
1003  " </tr>\n";
1004 
1005  // bcc line, if any
1006  if ( strategy->showHeader( "bcc" ) && !message->bcc().isEmpty() )
1007  headerStr +=
1008  " <tr> "
1009  " <td style=\"font-size: 6px; text-align: right; padding-left: 5px; padding-right: 24px; " + borderSettings + "\">" + i18n("BCC: ") + "</td> "
1010  " <td style=\"" + borderSettings + "\">" +
1011  KMMessage::emailAddrAsAnchor( message->bcc(), false, linkColor ) +
1012  " </td> "
1013  " </tr>\n";
1014 
1015  // header-bottom
1016  headerStr +=
1017  " </table> \n"
1018  " </td> \n"
1019  " <td style=\"min-width: 6px; max-height: 15px; background: url("+imgpath+"right.png); \"></td> \n"
1020  " </tr> \n"
1021  " <tr> \n"
1022  " <td style=\"min-width: 6px; background: url("+imgpath+"s_left.png); \"></td> \n"
1023  " <td style=\"height: 35px; width: 80%; background: url("+imgpath+"sbar.png);\"> \n"
1024  " <img src=\""+imgpath+"sw.png\" style=\"margin: 0px; height: 30px; overflow:hidden; \"> \n"
1025  " <img src=\""+imgpath+"sp_right.png\" style=\"float: right; \"> </td> \n"
1026  " <td style=\"min-width: 6px; background: url("+imgpath+"s_right.png); \"></td> \n"
1027  " </tr> \n"
1028  " </table> \n";
1029 
1030  // kmail icon
1031  if(topLevel) {
1032 
1033  // attachments
1034  headerStr +=
1035  "<div class=\"noprint\" style=\"position: absolute; top: 60px; right: 20px; width: 91px; height: 200px;\">"
1036  "<div id=\"attachmentInjectionPoint\"></div>"
1037  "</div>\n";
1038  }
1039 
1040  if ( printing ) {
1041  //provide a bit more left padding when printing
1042  //kolab/issue3254 (printed mail cut at the left side)
1043  headerStr += "<div style=\"padding: 6px; padding-left: 10px;\">";
1044  } else {
1045  headerStr += "<div style=\"padding: 6px;\">";
1046  }
1047 
1048  // TODO
1049  // spam status
1050  // ### iterate over the rest of strategy->headerToDisplay() (or
1051  // ### all headers if DefaultPolicy == Display) (elsewhere, too)
1052  return headerStr;
1053  }
1054 
1055 // #####################
1056 
1057  //
1058  // HeaderStyle abstract base:
1059  //
1060 
1061  HeaderStyle::HeaderStyle() {
1062 
1063  }
1064 
1065  HeaderStyle::~HeaderStyle() {
1066 
1067  }
1068 
1069  const HeaderStyle * HeaderStyle::create( Type type ) {
1070  switch ( type ) {
1071  case Brief: return brief();
1072  case Plain: return plain();
1073  case Fancy: return fancy();
1074  case Enterprise: return enterprise();
1075  }
1076  kdFatal( 5006 ) << "HeaderStyle::create(): Unknown header style ( type == "
1077  << (int)type << " ) requested!" << endl;
1078  return 0; // make compiler happy
1079  }
1080 
1081  const HeaderStyle * HeaderStyle::create( const TQString & type ) {
1082  TQString lowerType = type.lower();
1083  if ( lowerType == "brief" ) return brief();
1084  if ( lowerType == "plain" ) return plain();
1085  if ( lowerType == "enterprise" ) return enterprise();
1086  //if ( lowerType == "fancy" ) return fancy(); // not needed, see below
1087  // don't kdFatal here, b/c the strings are user-provided
1088  // (KConfig), so fail gracefully to the default:
1089  return fancy();
1090  }
1091 
1092  static const HeaderStyle * briefStyle = 0;
1093  static const HeaderStyle * plainStyle = 0;
1094  static const HeaderStyle * fancyStyle = 0;
1095  static const HeaderStyle * enterpriseStyle = 0;
1096 
1097  const HeaderStyle * HeaderStyle::brief() {
1098  if ( !briefStyle )
1099  briefStyle = new BriefHeaderStyle();
1100  return briefStyle;
1101  }
1102 
1103  const HeaderStyle * HeaderStyle::plain() {
1104  if ( !plainStyle )
1105  plainStyle = new PlainHeaderStyle();
1106  return plainStyle;
1107  }
1108 
1109  const HeaderStyle * HeaderStyle::fancy() {
1110  if ( !fancyStyle )
1111  fancyStyle = new FancyHeaderStyle();
1112  return fancyStyle;
1113  }
1114 
1115  const HeaderStyle * HeaderStyle::enterprise() {
1116  if ( !enterpriseStyle )
1117  enterpriseStyle = new EnterpriseHeaderStyle();
1118  return enterpriseStyle;
1119  }
1120 
1121 } // namespace KMail