kmail

templateparser.cpp
1 /*
2  * kmail: KDE mail client
3  * This file: Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  */
20 
21 #include <config.h>
22 
23 #include <tqstring.h>
24 #include <tqdatetime.h>
25 #include <tdelocale.h>
26 #include <kcalendarsystem.h>
27 #include <kmime_util.h>
28 #include <tdeglobal.h>
29 #include <tdeprocess.h>
30 #include <tqregexp.h>
31 #include <tqfile.h>
32 #include <tdemessagebox.h>
33 #include <kshell.h>
34 #include <tqfileinfo.h>
35 
36 #include "kmmessage.h"
37 #include "kmmsgbase.h"
38 #include "kmfolder.h"
39 #include "templatesconfiguration.h"
40 #include "templatesconfiguration_kfg.h"
41 #include "customtemplates_kfg.h"
42 #include "globalsettings_base.h"
43 #include "kmkernel.h"
44 #include <libkpimidentities/identity.h>
45 #include <libkpimidentities/identitymanager.h>
46 #include "partNode.h"
47 #include "attachmentcollector.h"
48 #include "objecttreeparser.h"
49 #include "util.h"
50 
51 #include "templateparser.h"
52 #include <mimelib/bodypart.h>
53 
54 using namespace KMail;
55 
56 TemplateParser::TemplateParser( KMMessage *amsg, const Mode amode ) :
57  mMode( amode ), mFolder( 0 ), mIdentity( 0 ),
58  mAllowDecryption( false ),
59  mDebug( false ), mQuoteString( "> " ), mAppend( false ), mOrigRoot( 0 )
60 {
61  mMsg = amsg;
62 }
63 
64 void TemplateParser::setSelection( const TQString &selection )
65 {
66  mSelection = selection;
67 }
68 
69 void TemplateParser::setAllowDecryption( const bool allowDecryption )
70 {
71  mAllowDecryption = allowDecryption;
72 }
73 
75 {
76  // Only strip the signature when replying, it should be preserved when forwarding
77  return ( mMode == Reply || mMode == ReplyAll) && GlobalSettings::stripSignature();
78 }
79 
80 TemplateParser::~TemplateParser()
81 {
82  delete mOrigRoot;
83  mOrigRoot = 0;
84 }
85 
86 int TemplateParser::parseQuotes( const TQString &prefix, const TQString &str,
87  TQString &quote ) const
88 {
89  int pos = prefix.length();
90  int len;
91  int str_len = str.length();
92  TQChar qc = '"';
93  TQChar prev = 0;
94 
95  pos++;
96  len = pos;
97 
98  while ( pos < str_len ) {
99  TQChar c = str[pos];
100 
101  pos++;
102  len++;
103 
104  if ( prev ) {
105  quote.append( c );
106  prev = 0;
107  } else {
108  if ( c == '\\' ) {
109  prev = c;
110  } else if ( c == qc ) {
111  break;
112  } else {
113  quote.append( c );
114  }
115  }
116  }
117 
118  return len;
119 }
120 
121 TQString TemplateParser::getFName( const TQString &str )
122 {
123  // simple logic:
124  // if there is ',' in name, than format is 'Last, First'
125  // else format is 'First Last'
126  // last resort -- return 'name' from 'name@domain'
127  int sep_pos;
128  TQString res;
129  if ( ( sep_pos = str.find( '@' ) ) > 0 ) {
130  int i;
131  for ( i = (sep_pos - 1); i >= 0; --i ) {
132  TQChar c = str[i];
133  if ( c.isLetterOrNumber() ) {
134  res.prepend( c );
135  } else {
136  break;
137  }
138  }
139  } else if ( ( sep_pos = str.find(',') ) > 0 ) {
140  unsigned int i;
141  bool begin = false;
142  for ( i = sep_pos; i < str.length(); ++i ) {
143  TQChar c = str[i];
144  if ( c.isLetterOrNumber() ) {
145  begin = true;
146  res.append( c );
147  } else if ( begin ) {
148  break;
149  }
150  }
151  } else {
152  unsigned int i;
153  for ( i = 0; i < str.length(); ++i ) {
154  TQChar c = str[i];
155  if ( c.isLetterOrNumber() ) {
156  res.append( c );
157  } else {
158  break;
159  }
160  }
161  }
162  return res;
163 }
164 
165 TQString TemplateParser::getLName( const TQString &str )
166 {
167  // simple logic:
168  // if there is ',' in name, than format is 'Last, First'
169  // else format is 'First Last'
170  int sep_pos;
171  TQString res;
172  if ( ( sep_pos = str.find(',') ) > 0 ) {
173  int i;
174  for ( i = sep_pos; i >= 0; --i ) {
175  TQChar c = str[i];
176  if ( c.isLetterOrNumber() ) {
177  res.prepend( c );
178  } else {
179  break;
180  }
181  }
182  } else {
183  if ( ( sep_pos = str.find( ' ' ) ) > 0 ) {
184  unsigned int i;
185  bool begin = false;
186  for ( i = sep_pos; i < str.length(); ++i ) {
187  TQChar c = str[i];
188  if ( c.isLetterOrNumber() ) {
189  begin = true;
190  res.append( c );
191  } else if ( begin ) {
192  break;
193  }
194  }
195  }
196  }
197  return res;
198 }
199 
200 void TemplateParser::process( KMMessage *aorig_msg, KMFolder *afolder, bool append )
201 {
202  mAppend = append;
203  mOrigMsg = aorig_msg;
204  mFolder = afolder;
205  TQString tmpl = findTemplate();
206  return processWithTemplate( tmpl );
207 }
208 
209 void TemplateParser::process( const TQString &tmplName, KMMessage *aorig_msg,
210  KMFolder *afolder, bool append )
211 {
212  mAppend = append;
213  mOrigMsg = aorig_msg;
214  mFolder = afolder;
215  TQString tmpl = findCustomTemplate( tmplName );
216  return processWithTemplate( tmpl );
217 }
218 
219 void TemplateParser::processWithTemplate( const TQString &tmpl )
220 {
221  TQString body;
222  int tmpl_len = tmpl.length();
223  bool dnl = false;
224  for ( int i = 0; i < tmpl_len; ++i ) {
225  TQChar c = tmpl[i];
226  // kdDebug() << "Next char: " << c << endl;
227  if ( c == '%' ) {
228  TQString cmd = tmpl.mid( i + 1 );
229 
230  if ( cmd.startsWith( "-" ) ) {
231  // dnl
232  kdDebug() << "Command: -" << endl;
233  dnl = true;
234  i += 1;
235 
236  } else if ( cmd.startsWith( "REM=" ) ) {
237  // comments
238  kdDebug() << "Command: REM=" << endl;
239  TQString q;
240  int len = parseQuotes( "REM=", cmd, q );
241  i += len;
242 
243  } else if ( cmd.startsWith( "INSERT=" ) ) {
244  // insert content of specified file as is
245  kdDebug() << "Command: INSERT=" << endl;
246  TQString q;
247  int len = parseQuotes( "INSERT=", cmd, q );
248  i += len;
249  TQString path = KShell::tildeExpand( q );
250  TQFileInfo finfo( path );
251  if (finfo.isRelative() ) {
252  path = KShell::homeDir( "" );
253  path += '/';
254  path += q;
255  }
256  TQFile file( path );
257  if ( file.open( IO_ReadOnly ) ) {
258  TQByteArray content = file.readAll();
259  TQString str = TQString::fromLocal8Bit( content, content.size() );
260  body.append( str );
261  } else if ( mDebug ) {
262  KMessageBox::error( 0,
263  i18n( "Cannot insert content from file %1: %2" ).
264  arg( path ).arg( file.errorString() ) );
265  }
266 
267  } else if ( cmd.startsWith( "SYSTEM=" ) ) {
268  // insert content of specified file as is
269  kdDebug() << "Command: SYSTEM=" << endl;
270  TQString q;
271  int len = parseQuotes( "SYSTEM=", cmd, q );
272  i += len;
273  TQString pipe_cmd = q;
274  TQString str = pipe( pipe_cmd, "" );
275  body.append( str );
276 
277  } else if ( cmd.startsWith( "PUT=" ) ) {
278  // insert content of specified file as is
279  kdDebug() << "Command: PUT=" << endl;
280  TQString q;
281  int len = parseQuotes( "PUT=", cmd, q );
282  i += len;
283  TQString path = KShell::tildeExpand( q );
284  TQFileInfo finfo( path );
285  if (finfo.isRelative() ) {
286  path = KShell::homeDir( "" );
287  path += '/';
288  path += q;
289  }
290  TQFile file( path );
291  if ( file.open( IO_ReadOnly ) ) {
292  TQByteArray content = file.readAll();
293  body.append( TQString::fromLocal8Bit( content, content.size() ) );
294  } else if ( mDebug ) {
295  KMessageBox::error( 0,
296  i18n( "Cannot insert content from file %1: %2").
297  arg( path ).arg(file.errorString() ));
298  }
299 
300  } else if ( cmd.startsWith( "QUOTEPIPE=" ) ) {
301  // pipe message body throw command and insert it as quotation
302  kdDebug() << "Command: QUOTEPIPE=" << endl;
303  TQString q;
304  int len = parseQuotes( "QUOTEPIPE=", cmd, q );
305  i += len;
306  TQString pipe_cmd = q;
307  if ( mOrigMsg ) {
308  TQString str = pipe( pipe_cmd, messageText( false ) );
309  TQString quote = mOrigMsg->asQuotedString( "", mQuoteString, str,
310  shouldStripSignature(), mAllowDecryption );
311  body.append( quote );
312  }
313 
314  } else if ( cmd.startsWith( "QUOTE" ) ) {
315  kdDebug() << "Command: QUOTE" << endl;
316  i += strlen( "QUOTE" );
317  if ( mOrigMsg ) {
318  TQString quote = mOrigMsg->asQuotedString( "", mQuoteString, messageText( true ),
319  shouldStripSignature(), mAllowDecryption );
320  body.append( quote );
321  }
322 
323  } else if ( cmd.startsWith( "QHEADERS" ) ) {
324  kdDebug() << "Command: TQHEADERS" << endl;
325  i += strlen( "QHEADERS" );
326  if ( mOrigMsg ) {
327  TQString quote = mOrigMsg->asQuotedString( "", mQuoteString,
328  mOrigMsg->headerAsSendableString(),
329  false, false );
330  body.append( quote );
331  }
332 
333  } else if ( cmd.startsWith( "HEADERS" ) ) {
334  kdDebug() << "Command: HEADERS" << endl;
335  i += strlen( "HEADERS" );
336  if ( mOrigMsg ) {
337  TQString str = mOrigMsg->headerAsSendableString();
338  body.append( str );
339  }
340 
341  } else if ( cmd.startsWith( "TEXTPIPE=" ) ) {
342  // pipe message body throw command and insert it as is
343  kdDebug() << "Command: TEXTPIPE=" << endl;
344  TQString q;
345  int len = parseQuotes( "TEXTPIPE=", cmd, q );
346  i += len;
347  TQString pipe_cmd = q;
348  if ( mOrigMsg ) {
349  TQString str = pipe(pipe_cmd, messageText( false ) );
350  body.append( str );
351  }
352 
353  } else if ( cmd.startsWith( "MSGPIPE=" ) ) {
354  // pipe full message throw command and insert result as is
355  kdDebug() << "Command: MSGPIPE=" << endl;
356  TQString q;
357  int len = parseQuotes( "MSGPIPE=", cmd, q );
358  i += len;
359  TQString pipe_cmd = q;
360  if ( mOrigMsg ) {
361  TQString str = pipe(pipe_cmd, mOrigMsg->asString() );
362  body.append( str );
363  }
364 
365  } else if ( cmd.startsWith( "BODYPIPE=" ) ) {
366  // pipe message body generated so far throw command and insert result as is
367  kdDebug() << "Command: BODYPIPE=" << endl;
368  TQString q;
369  int len = parseQuotes( "BODYPIPE=", cmd, q );
370  i += len;
371  TQString pipe_cmd = q;
372  TQString str = pipe( pipe_cmd, body );
373  body.append( str );
374 
375  } else if ( cmd.startsWith( "CLEARPIPE=" ) ) {
376  // pipe message body generated so far throw command and
377  // insert result as is replacing current body
378  kdDebug() << "Command: CLEARPIPE=" << endl;
379  TQString q;
380  int len = parseQuotes( "CLEARPIPE=", cmd, q );
381  i += len;
382  TQString pipe_cmd = q;
383  TQString str = pipe( pipe_cmd, body );
384  body = str;
385  mMsg->setCursorPos( 0 );
386 
387  } else if ( cmd.startsWith( "TEXT" ) ) {
388  kdDebug() << "Command: TEXT" << endl;
389  i += strlen( "TEXT" );
390  if ( mOrigMsg ) {
391  TQString quote = messageText( false );
392  body.append( quote );
393  }
394 
395  } else if ( cmd.startsWith( "OTEXTSIZE" ) ) {
396  kdDebug() << "Command: OTEXTSIZE" << endl;
397  i += strlen( "OTEXTSIZE" );
398  if ( mOrigMsg ) {
399  TQString str = TQString( "%1" ).arg( mOrigMsg->body().length() );
400  body.append( str );
401  }
402 
403  } else if ( cmd.startsWith( "OTEXT" ) ) {
404  kdDebug() << "Command: OTEXT" << endl;
405  i += strlen( "OTEXT" );
406  if ( mOrigMsg ) {
407  TQString quote = messageText( false );
408  body.append( quote );
409  }
410 
411  } else if ( cmd.startsWith( "OADDRESSEESADDR" ) ) {
412  kdDebug() << "Command: OADDRESSEESADDR" << endl;
413  i += strlen( "OADDRESSEESADDR" );
414  const TQString to = mOrigMsg->to();
415  const TQString cc = mOrigMsg->cc();
416  if ( !to.isEmpty() )
417  body.append( i18n( "To:" ) + ' ' + to );
418  if ( !to.isEmpty() && !cc.isEmpty() )
419  body.append( '\n' );
420  if ( !cc.isEmpty() )
421  body.append( i18n( "CC:" ) + ' ' + cc );
422 
423  } else if ( cmd.startsWith( "CCADDR" ) ) {
424  kdDebug() << "Command: CCADDR" << endl;
425  i += strlen( "CCADDR" );
426  TQString str = mMsg->cc();
427  body.append( str );
428 
429  } else if ( cmd.startsWith( "CCNAME" ) ) {
430  kdDebug() << "Command: CCNAME" << endl;
431  i += strlen( "CCNAME" );
432  TQString str = mMsg->ccStrip();
433  body.append( str );
434 
435  } else if ( cmd.startsWith( "CCFNAME" ) ) {
436  kdDebug() << "Command: CCFNAME" << endl;
437  i += strlen( "CCFNAME" );
438  TQString str = mMsg->ccStrip();
439  body.append( getFName( str ) );
440 
441  } else if ( cmd.startsWith( "CCLNAME" ) ) {
442  kdDebug() << "Command: CCLNAME" << endl;
443  i += strlen( "CCLNAME" );
444  TQString str = mMsg->ccStrip();
445  body.append( getLName( str ) );
446 
447  } else if ( cmd.startsWith( "TOADDR" ) ) {
448  kdDebug() << "Command: TOADDR" << endl;
449  i += strlen( "TOADDR" );
450  TQString str = mMsg->to();
451  body.append( str );
452 
453  } else if ( cmd.startsWith( "TONAME" ) ) {
454  kdDebug() << "Command: TONAME" << endl;
455  i += strlen( "TONAME" );
456  TQString str = mMsg->toStrip();
457  body.append( str );
458 
459  } else if ( cmd.startsWith( "TOFNAME" ) ) {
460  kdDebug() << "Command: TOFNAME" << endl;
461  i += strlen( "TOFNAME" );
462  TQString str = mMsg->toStrip();
463  body.append( getFName( str ) );
464 
465  } else if ( cmd.startsWith( "TOLNAME" ) ) {
466  kdDebug() << "Command: TOLNAME" << endl;
467  i += strlen( "TOLNAME" );
468  TQString str = mMsg->toStrip();
469  body.append( getLName( str ) );
470 
471  } else if ( cmd.startsWith( "TOLIST" ) ) {
472  kdDebug() << "Command: TOLIST" << endl;
473  i += strlen( "TOLIST" );
474  TQString str = mMsg->to();
475  body.append( str );
476 
477  } else if ( cmd.startsWith( "FROMADDR" ) ) {
478  kdDebug() << "Command: FROMADDR" << endl;
479  i += strlen( "FROMADDR" );
480  TQString str = mMsg->from();
481  body.append( str );
482 
483  } else if ( cmd.startsWith( "FROMNAME" ) ) {
484  kdDebug() << "Command: FROMNAME" << endl;
485  i += strlen( "FROMNAME" );
486  TQString str = mMsg->fromStrip();
487  body.append( str );
488 
489  } else if ( cmd.startsWith( "FROMFNAME" ) ) {
490  kdDebug() << "Command: FROMFNAME" << endl;
491  i += strlen( "FROMFNAME" );
492  TQString str = mMsg->fromStrip();
493  body.append( getFName( str ) );
494 
495  } else if ( cmd.startsWith( "FROMLNAME" ) ) {
496  kdDebug() << "Command: FROMLNAME" << endl;
497  i += strlen( "FROMLNAME" );
498  TQString str = mMsg->fromStrip();
499  body.append( getLName( str ) );
500 
501  } else if ( cmd.startsWith( "FULLSUBJECT" ) ) {
502  kdDebug() << "Command: FULLSUBJECT" << endl;
503  i += strlen( "FULLSUBJECT" );
504  TQString str = mMsg->subject();
505  body.append( str );
506 
507  } else if ( cmd.startsWith( "FULLSUBJ" ) ) {
508  kdDebug() << "Command: FULLSUBJ" << endl;
509  i += strlen( "FULLSUBJ" );
510  TQString str = mMsg->subject();
511  body.append( str );
512 
513  } else if ( cmd.startsWith( "MSGID" ) ) {
514  kdDebug() << "Command: MSGID" << endl;
515  i += strlen( "MSGID" );
516  TQString str = mMsg->id();
517  body.append( str );
518 
519  } else if ( cmd.startsWith( "OHEADER=" ) ) {
520  // insert specified content of header from original message
521  kdDebug() << "Command: OHEADER=" << endl;
522  TQString q;
523  int len = parseQuotes( "OHEADER=", cmd, q );
524  i += len;
525  if ( mOrigMsg ) {
526  TQString hdr = q;
527  TQString str = mOrigMsg->headerFields(hdr.local8Bit() ).join( ", " );
528  body.append( str );
529  }
530 
531  } else if ( cmd.startsWith( "HEADER=" ) ) {
532  // insert specified content of header from current message
533  kdDebug() << "Command: HEADER=" << endl;
534  TQString q;
535  int len = parseQuotes( "HEADER=", cmd, q );
536  i += len;
537  TQString hdr = q;
538  TQString str = mMsg->headerFields(hdr.local8Bit() ).join( ", " );
539  body.append( str );
540 
541  } else if ( cmd.startsWith( "HEADER( " ) ) {
542  // insert specified content of header from current message
543  kdDebug() << "Command: HEADER( " << endl;
544  TQRegExp re = TQRegExp( "^HEADER\\((.+)\\)" );
545  re.setMinimal( true );
546  int res = re.search( cmd );
547  if ( res != 0 ) {
548  // something wrong
549  i += strlen( "HEADER( " );
550  } else {
551  i += re.matchedLength();
552  TQString hdr = re.cap( 1 );
553  TQString str = mMsg->headerFields( hdr.local8Bit() ).join( ", " );
554  body.append( str );
555  }
556 
557  } else if ( cmd.startsWith( "OCCADDR" ) ) {
558  kdDebug() << "Command: OCCADDR" << endl;
559  i += strlen( "OCCADDR" );
560  if ( mOrigMsg ) {
561  TQString str = mOrigMsg->cc();
562  body.append( str );
563  }
564 
565  } else if ( cmd.startsWith( "OCCNAME" ) ) {
566  kdDebug() << "Command: OCCNAME" << endl;
567  i += strlen( "OCCNAME" );
568  if ( mOrigMsg ) {
569  TQString str = mOrigMsg->ccStrip();
570  body.append( str );
571  }
572 
573  } else if ( cmd.startsWith( "OCCFNAME" ) ) {
574  kdDebug() << "Command: OCCFNAME" << endl;
575  i += strlen( "OCCFNAME" );
576  if ( mOrigMsg ) {
577  TQString str = mOrigMsg->ccStrip();
578  body.append( getFName( str ) );
579  }
580 
581  } else if ( cmd.startsWith( "OCCLNAME" ) ) {
582  kdDebug() << "Command: OCCLNAME" << endl;
583  i += strlen( "OCCLNAME" );
584  if ( mOrigMsg ) {
585  TQString str = mOrigMsg->ccStrip();
586  body.append( getLName( str ) );
587  }
588 
589  } else if ( cmd.startsWith( "OTOADDR" ) ) {
590  kdDebug() << "Command: OTOADDR" << endl;
591  i += strlen( "OTOADDR" );
592  if ( mOrigMsg ) {
593  TQString str = mOrigMsg->to();
594  body.append( str );
595  }
596 
597  } else if ( cmd.startsWith( "OTONAME" ) ) {
598  kdDebug() << "Command: OTONAME" << endl;
599  i += strlen( "OTONAME" );
600  if ( mOrigMsg ) {
601  TQString str = mOrigMsg->toStrip();
602  body.append( str );
603  }
604 
605  } else if ( cmd.startsWith( "OTOFNAME" ) ) {
606  kdDebug() << "Command: OTOFNAME" << endl;
607  i += strlen( "OTOFNAME" );
608  if ( mOrigMsg ) {
609  TQString str = mOrigMsg->toStrip();
610  body.append( getFName( str ) );
611  }
612 
613  } else if ( cmd.startsWith( "OTOLNAME" ) ) {
614  kdDebug() << "Command: OTOLNAME" << endl;
615  i += strlen( "OTOLNAME" );
616  if ( mOrigMsg ) {
617  TQString str = mOrigMsg->toStrip();
618  body.append( getLName( str ) );
619  }
620 
621  } else if ( cmd.startsWith( "OTOLIST" ) ) {
622  kdDebug() << "Command: OTOLIST" << endl;
623  i += strlen( "OTOLIST" );
624  if ( mOrigMsg ) {
625  TQString str = mOrigMsg->to();
626  body.append( str );
627  }
628 
629  } else if ( cmd.startsWith( "OTO" ) ) {
630  kdDebug() << "Command: OTO" << endl;
631  i += strlen( "OTO" );
632  if ( mOrigMsg ) {
633  TQString str = mOrigMsg->to();
634  body.append( str );
635  }
636 
637  } else if ( cmd.startsWith( "OFROMADDR" ) ) {
638  kdDebug() << "Command: OFROMADDR" << endl;
639  i += strlen( "OFROMADDR" );
640  if ( mOrigMsg ) {
641  TQString str = mOrigMsg->from();
642  body.append( str );
643  }
644 
645  } else if ( cmd.startsWith( "OFROMNAME" ) ) {
646  kdDebug() << "Command: OFROMNAME" << endl;
647  i += strlen( "OFROMNAME" );
648  if ( mOrigMsg ) {
649  TQString str = mOrigMsg->fromStrip();
650  body.append( str );
651  }
652 
653  } else if ( cmd.startsWith( "OFROMFNAME" ) ) {
654  kdDebug() << "Command: OFROMFNAME" << endl;
655  i += strlen( "OFROMFNAME" );
656  if ( mOrigMsg ) {
657  TQString str = mOrigMsg->fromStrip();
658  body.append( getFName( str ) );
659  }
660 
661  } else if ( cmd.startsWith( "OFROMLNAME" ) ) {
662  kdDebug() << "Command: OFROMLNAME" << endl;
663  i += strlen( "OFROMLNAME" );
664  if ( mOrigMsg ) {
665  TQString str = mOrigMsg->fromStrip();
666  body.append( getLName( str ) );
667  }
668 
669  } else if ( cmd.startsWith( "OFULLSUBJECT" ) ) {
670  kdDebug() << "Command: OFULLSUBJECT" << endl;
671  i += strlen( "OFULLSUBJECT" );
672  if ( mOrigMsg ) {
673  TQString str = mOrigMsg->subject();
674  body.append( str );
675  }
676 
677  } else if ( cmd.startsWith( "OFULLSUBJ" ) ) {
678  kdDebug() << "Command: OFULLSUBJ" << endl;
679  i += strlen( "OFULLSUBJ" );
680  if ( mOrigMsg ) {
681  TQString str = mOrigMsg->subject();
682  body.append( str );
683  }
684 
685  } else if ( cmd.startsWith( "OMSGID" ) ) {
686  kdDebug() << "Command: OMSGID" << endl;
687  i += strlen( "OMSGID" );
688  if ( mOrigMsg ) {
689  TQString str = mOrigMsg->id();
690  body.append( str );
691  }
692 
693  } else if ( cmd.startsWith( "DATEEN" ) ) {
694  kdDebug() << "Command: DATEEN" << endl;
695  i += strlen( "DATEEN" );
696  TQDateTime date = TQDateTime::currentDateTime();
697  TDELocale locale( "C" );
698  TQString str = locale.formatDate( date.date(), false );
699  body.append( str );
700 
701  } else if ( cmd.startsWith( "DATESHORT" ) ) {
702  kdDebug() << "Command: DATESHORT" << endl;
703  i += strlen( "DATESHORT" );
704  TQDateTime date = TQDateTime::currentDateTime();
705  TQString str = TDEGlobal::locale()->formatDate( date.date(), true );
706  body.append( str );
707 
708  } else if ( cmd.startsWith( "DATE" ) ) {
709  kdDebug() << "Command: DATE" << endl;
710  i += strlen( "DATE" );
711  TQDateTime date = TQDateTime::currentDateTime();
712  TQString str = TDEGlobal::locale()->formatDate( date.date(), false );
713  body.append( str );
714 
715  } else if ( cmd.startsWith( "DOW" ) ) {
716  kdDebug() << "Command: DOW" << endl;
717  i += strlen( "DOW" );
718  TQDateTime date = TQDateTime::currentDateTime();
719  TQString str = TDEGlobal::locale()->calendar()->weekDayName( date.date(), false );
720  body.append( str );
721 
722  } else if ( cmd.startsWith( "TIMELONGEN" ) ) {
723  kdDebug() << "Command: TIMELONGEN" << endl;
724  i += strlen( "TIMELONGEN" );
725  TQDateTime date = TQDateTime::currentDateTime();
726  TDELocale locale( "C");
727  TQString str = locale.formatTime( date.time(), true );
728  body.append( str );
729 
730  } else if ( cmd.startsWith( "TIMELONG" ) ) {
731  kdDebug() << "Command: TIMELONG" << endl;
732  i += strlen( "TIMELONG" );
733  TQDateTime date = TQDateTime::currentDateTime();
734  TQString str = TDEGlobal::locale()->formatTime( date.time(), true );
735  body.append( str );
736 
737  } else if ( cmd.startsWith( "TIME" ) ) {
738  kdDebug() << "Command: TIME" << endl;
739  i += strlen( "TIME" );
740  TQDateTime date = TQDateTime::currentDateTime();
741  TQString str = TDEGlobal::locale()->formatTime( date.time(), false );
742  body.append( str );
743 
744  } else if ( cmd.startsWith( "ODATEEN" ) ) {
745  kdDebug() << "Command: ODATEEN" << endl;
746  i += strlen( "ODATEEN" );
747  if ( mOrigMsg ) {
748  TQDateTime date;
749  date.setTime_t( mOrigMsg->date() );
750  TDELocale locale( "C");
751  TQString str = locale.formatDate( date.date(), false );
752  body.append( str );
753  }
754 
755  } else if ( cmd.startsWith( "ODATESHORT") ) {
756  kdDebug() << "Command: ODATESHORT" << endl;
757  i += strlen( "ODATESHORT");
758  if ( mOrigMsg ) {
759  TQDateTime date;
760  date.setTime_t( mOrigMsg->date() );
761  TQString str = TDEGlobal::locale()->formatDate( date.date(), true );
762  body.append( str );
763  }
764 
765  } else if ( cmd.startsWith( "ODATE") ) {
766  kdDebug() << "Command: ODATE" << endl;
767  i += strlen( "ODATE");
768  if ( mOrigMsg ) {
769  TQDateTime date;
770  date.setTime_t( mOrigMsg->date() );
771  TQString str = TDEGlobal::locale()->formatDate( date.date(), false );
772  body.append( str );
773  }
774 
775  } else if ( cmd.startsWith( "ODOW") ) {
776  kdDebug() << "Command: ODOW" << endl;
777  i += strlen( "ODOW");
778  if ( mOrigMsg ) {
779  TQDateTime date;
780  date.setTime_t( mOrigMsg->date() );
781  TQString str = TDEGlobal::locale()->calendar()->weekDayName( date.date(), false );
782  body.append( str );
783  }
784 
785  } else if ( cmd.startsWith( "OTIMELONGEN") ) {
786  kdDebug() << "Command: OTIMELONGEN" << endl;
787  i += strlen( "OTIMELONGEN");
788  if ( mOrigMsg ) {
789  TQDateTime date;
790  date.setTime_t( mOrigMsg->date() );
791  TDELocale locale( "C");
792  TQString str = locale.formatTime( date.time(), true );
793  body.append( str );
794  }
795 
796  } else if ( cmd.startsWith( "OTIMELONG") ) {
797  kdDebug() << "Command: OTIMELONG" << endl;
798  i += strlen( "OTIMELONG");
799  if ( mOrigMsg ) {
800  TQDateTime date;
801  date.setTime_t( mOrigMsg->date() );
802  TQString str = TDEGlobal::locale()->formatTime( date.time(), true );
803  body.append( str );
804  }
805 
806  } else if ( cmd.startsWith( "OTIME") ) {
807  kdDebug() << "Command: OTIME" << endl;
808  i += strlen( "OTIME");
809  if ( mOrigMsg ) {
810  TQDateTime date;
811  date.setTime_t( mOrigMsg->date() );
812  TQString str = TDEGlobal::locale()->formatTime( date.time(), false );
813  body.append( str );
814  }
815 
816  } else if ( cmd.startsWith( "BLANK" ) ) {
817  // do nothing
818  kdDebug() << "Command: BLANK" << endl;
819  i += strlen( "BLANK" );
820 
821  } else if ( cmd.startsWith( "NOP" ) ) {
822  // do nothing
823  kdDebug() << "Command: NOP" << endl;
824  i += strlen( "NOP" );
825 
826  } else if ( cmd.startsWith( "CLEAR" ) ) {
827  // clear body buffer; not too useful yet
828  kdDebug() << "Command: CLEAR" << endl;
829  i += strlen( "CLEAR" );
830  body = "";
831  mMsg->setCursorPos( 0 );
832 
833  } else if ( cmd.startsWith( "DEBUGOFF" ) ) {
834  // turn off debug
835  kdDebug() << "Command: DEBUGOFF" << endl;
836  i += strlen( "DEBUGOFF" );
837  mDebug = false;
838 
839  } else if ( cmd.startsWith( "DEBUG" ) ) {
840  // turn on debug
841  kdDebug() << "Command: DEBUG" << endl;
842  i += strlen( "DEBUG" );
843  mDebug = true;
844 
845  } else if ( cmd.startsWith( "CURSOR" ) ) {
846  // turn on debug
847  kdDebug() << "Command: CURSOR" << endl;
848  i += strlen( "CURSOR" );
849  mMsg->setCursorPos( body.length() );
850 
851  } else {
852  // wrong command, do nothing
853  body.append( c );
854  }
855 
856  } else if ( dnl && ( c == '\n' || c == '\r') ) {
857  // skip
858  if ( ( c == '\n' && tmpl[i + 1] == '\r' ) ||
859  ( c == '\r' && tmpl[i + 1] == '\n' ) ) {
860  // skip one more
861  i += 1;
862  }
863  dnl = false;
864  } else {
865  body.append( c );
866  }
867  }
868 
870 }
871 
872 TQString TemplateParser::messageText( bool allowSelectionOnly )
873 {
874  if ( !mSelection.isEmpty() && allowSelectionOnly )
875  return mSelection;
876 
877  // No selection text, therefore we need to parse the object tree ourselves to get
878  partNode *root = parsedObjectTree();
879  return mOrigMsg->asPlainTextFromObjectTree( root, shouldStripSignature(), mAllowDecryption );
880 }
881 
883 {
884  if ( mOrigRoot )
885  return mOrigRoot;
886 
887  mOrigRoot = partNode::fromMessage( mOrigMsg );
888  ObjectTreeParser otp; // all defaults are ok
889  otp.parseObjectTree( mOrigRoot );
890  return mOrigRoot;
891 }
892 
893 void TemplateParser::addProcessedBodyToMessage( const TQString &body )
894 {
895  if ( mAppend ) {
896 
897  // ### What happens here if the body is multipart or in some way encoded?
898  TQCString msg_body = mMsg->body();
899  msg_body.append( body.utf8() );
900  mMsg->setBody( msg_body );
901  }
902  else {
903 
904  // Get the attachments of the original mail
905  partNode *root = parsedObjectTree();
906  AttachmentCollector ac;
907  ac.collectAttachmentsFrom( root );
908 
909  // Now, delete the old content and set the new content, which
910  // is either only the new text or the new text with some attachments.
911  mMsg->deleteBodyParts();
912 
913  // Set To and CC from the template
914  if ( mMode == Forward ) {
915  if ( !mTo.isEmpty() ) {
916  mMsg->setTo( mMsg->to() + ',' + mTo );
917  }
918  if ( !mCC.isEmpty() )
919  mMsg->setCc( mMsg->cc() + ',' + mCC );
920  }
921 
922  // If we have no attachment, simply create a text/plain part and
923  // set the processed template text as the body
924  if ( ac.attachments().empty() || mMode != Forward ) {
925  mMsg->headers().ContentType().FromString( DwString() ); // to get rid of old boundary
926  mMsg->headers().ContentType().Parse();
927  mMsg->headers().ContentType().SetType( DwMime::kTypeText );
928  mMsg->headers().ContentType().SetSubtype( DwMime::kSubtypePlain );
929  mMsg->headers().Assemble();
930  mMsg->setBodyFromUnicode( body );
931  mMsg->assembleIfNeeded();
932  }
933 
934  // If we have some attachments, create a multipart/mixed mail and
935  // add the normal body as well as the attachments
936  else
937  {
938  mMsg->headers().ContentType().SetType( DwMime::kTypeMultipart );
939  mMsg->headers().ContentType().SetSubtype( DwMime::kSubtypeMixed );
940  mMsg->headers().ContentType().CreateBoundary( 0 );
941 
942  KMMessagePart textPart;
943  textPart.setBodyFromUnicode( body );
944  mMsg->addDwBodyPart( mMsg->createDWBodyPart( &textPart ) );
945  mMsg->assembleIfNeeded();
946 
947  int attachmentNumber = 1;
948  for ( std::vector<partNode*>::const_iterator it = ac.attachments().begin();
949  it != ac.attachments().end(); ++it, attachmentNumber++ ) {
950 
951  // When adding this body part, make sure to _not_ add the next bodypart
952  // as well, which mimelib would do, therefore creating a mail with many
953  // duplicate attachments (so many that KMail runs out of memory, in fact).
954  // Body::AddBodyPart is very misleading here...
955  ( *it )->dwPart()->SetNext( 0 );
956 
957  DwBodyPart *cloned = static_cast<DwBodyPart*>( ( *it )->dwPart()->Clone() );
958 
959  // If the content type has no name or filename parameter, add one, since otherwise the name
960  // would be empty in the attachment view of the composer, which looks confusing
961  if ( cloned->Headers().HasContentType() ) {
962  DwMediaType &ct = cloned->Headers().ContentType();
963 
964  // Converting to a string here, since DwMediaType does not have a HasParameter() function
965  TQString ctStr = ct.AsString().c_str();
966  if ( !ctStr.lower().contains( "name=" ) && !ctStr.lower().contains( "filename=" ) ) {
967  DwParameter *nameParameter = new DwParameter;
968  nameParameter->SetAttribute( "name" );
969  nameParameter->SetValue( Util::dwString( KMMsgBase::encodeRFC2231StringAutoDetectCharset(
970  i18n( "Attachment %1" ).arg( attachmentNumber ) ) ) );
971  ct.AddParameter( nameParameter );
972  }
973  }
974 
975  mMsg->addDwBodyPart( cloned );
976  mMsg->assembleIfNeeded();
977  }
978  }
979  }
980 }
981 
982 TQString TemplateParser::findCustomTemplate( const TQString &tmplName )
983 {
984  CTemplates t( tmplName );
985  mTo = t.to();
986  mCC = t.cC();
987  TQString content = t.content();
988  if ( !content.isEmpty() ) {
989  return content;
990  } else {
991  return findTemplate();
992  }
993 }
994 
996 {
997  // import 'Phrases' if it not done yet
998  if ( !GlobalSettings::self()->phrasesConverted() ) {
999  TemplatesConfiguration::importFromPhrases();
1000  }
1001 
1002  // kdDebug() << "Trying to find template for mode " << mode << endl;
1003 
1004  TQString tmpl;
1005 
1006  if ( !mFolder ) { // find folder message belongs to
1007  mFolder = mMsg->parent();
1008  if ( !mFolder ) {
1009  if ( mOrigMsg ) {
1010  mFolder = mOrigMsg->parent();
1011  }
1012  if ( !mFolder ) {
1013  kdDebug(5006) << "Oops! No folder for message" << endl;
1014  }
1015  }
1016  }
1017  kdDebug(5006) << "Folder found: " << mFolder << endl;
1018 
1019  if ( mFolder ) // only if a folder was found
1020  {
1021  TQString fid = mFolder->idString();
1022  Templates fconf( fid );
1023  if ( fconf.useCustomTemplates() ) { // does folder use custom templates?
1024  switch( mMode ) {
1025  case NewMessage:
1026  tmpl = fconf.templateNewMessage();
1027  break;
1028  case Reply:
1029  tmpl = fconf.templateReply();
1030  break;
1031  case ReplyAll:
1032  tmpl = fconf.templateReplyAll();
1033  break;
1034  case Forward:
1035  tmpl = fconf.templateForward();
1036  break;
1037  default:
1038  kdDebug(5006) << "Unknown message mode: " << mMode << endl;
1039  return "";
1040  }
1041  mQuoteString = fconf.quoteString();
1042  if ( !tmpl.isEmpty() ) {
1043  return tmpl; // use folder-specific template
1044  }
1045  }
1046  }
1047 
1048  if ( !mIdentity ) { // find identity message belongs to
1049  mIdentity = mMsg->identityUoid();
1050  if ( !mIdentity && mOrigMsg ) {
1051  mIdentity = mOrigMsg->identityUoid();
1052  }
1053  mIdentity = kmkernel->identityManager()->identityForUoidOrDefault( mIdentity ).uoid();
1054  if ( !mIdentity ) {
1055  kdDebug(5006) << "Oops! No identity for message" << endl;
1056  }
1057  }
1058  kdDebug(5006) << "Identity found: " << mIdentity << endl;
1059 
1060  TQString iid;
1061  if ( mIdentity ) {
1062  iid = TQString("IDENTITY_%1").arg( mIdentity ); // templates ID for that identity
1063  }
1064  else {
1065  iid = "IDENTITY_NO_IDENTITY"; // templates ID for no identity
1066  }
1067 
1068  Templates iconf( iid );
1069  if ( iconf.useCustomTemplates() ) { // does identity use custom templates?
1070  switch( mMode ) {
1071  case NewMessage:
1072  tmpl = iconf.templateNewMessage();
1073  break;
1074  case Reply:
1075  tmpl = iconf.templateReply();
1076  break;
1077  case ReplyAll:
1078  tmpl = iconf.templateReplyAll();
1079  break;
1080  case Forward:
1081  tmpl = iconf.templateForward();
1082  break;
1083  default:
1084  kdDebug(5006) << "Unknown message mode: " << mMode << endl;
1085  return "";
1086  }
1087  mQuoteString = iconf.quoteString();
1088  if ( !tmpl.isEmpty() ) {
1089  return tmpl; // use identity-specific template
1090  }
1091  }
1092 
1093  switch( mMode ) { // use the global template
1094  case NewMessage:
1095  tmpl = GlobalSettings::self()->templateNewMessage();
1096  break;
1097  case Reply:
1098  tmpl = GlobalSettings::self()->templateReply();
1099  break;
1100  case ReplyAll:
1101  tmpl = GlobalSettings::self()->templateReplyAll();
1102  break;
1103  case Forward:
1104  tmpl = GlobalSettings::self()->templateForward();
1105  break;
1106  default:
1107  kdDebug(5006) << "Unknown message mode: " << mMode << endl;
1108  return "";
1109  }
1110 
1111  mQuoteString = GlobalSettings::self()->quoteString();
1112  return tmpl;
1113 }
1114 
1115 TQString TemplateParser::pipe( const TQString &cmd, const TQString &buf )
1116 {
1117  mPipeOut = "";
1118  mPipeErr = "";
1119  mPipeRc = 0;
1120 
1121  TDEProcess proc;
1122  TQCString data = buf.local8Bit();
1123 
1124  // kdDebug() << "Command data: " << data << endl;
1125 
1126  proc << KShell::splitArgs( cmd, KShell::TildeExpand );
1127  proc.setUseShell( true );
1128  connect( &proc, TQ_SIGNAL( receivedStdout( TDEProcess *, char *, int ) ),
1129  this, TQ_SLOT( onReceivedStdout( TDEProcess *, char *, int ) ) );
1130  connect( &proc, TQ_SIGNAL( receivedStderr( TDEProcess *, char *, int ) ),
1131  this, TQ_SLOT( onReceivedStderr( TDEProcess *, char *, int ) ) );
1132  connect( &proc, TQ_SIGNAL( wroteStdin( TDEProcess * ) ),
1133  this, TQ_SLOT( onWroteStdin( TDEProcess * ) ) );
1134 
1135  if ( proc.start( TDEProcess::NotifyOnExit, TDEProcess::All ) ) {
1136 
1137  bool pipe_filled = proc.writeStdin( data, data.length() );
1138  if ( pipe_filled ) {
1139  proc.closeStdin();
1140 
1141  bool exited = proc.wait( PipeTimeout );
1142  if ( exited ) {
1143 
1144  if ( proc.normalExit() ) {
1145 
1146  mPipeRc = proc.exitStatus();
1147  if ( mPipeRc != 0 && mDebug ) {
1148  if ( mPipeErr.isEmpty() ) {
1149  KMessageBox::error( 0,
1150  i18n( "Pipe command exit with status %1: %2").
1151  arg( mPipeRc ).arg( cmd ) );
1152  } else {
1153  KMessageBox::detailedError( 0,
1154  i18n( "Pipe command exit with status %1: %2" ).
1155  arg( mPipeRc ).arg( cmd ), mPipeErr );
1156  }
1157  }
1158 
1159  } else {
1160 
1161  mPipeRc = -( proc.exitSignal() );
1162  if ( mPipeRc != 0 && mDebug ) {
1163  if ( mPipeErr.isEmpty() ) {
1164  KMessageBox::error( 0,
1165  i18n( "Pipe command killed by signal %1: %2" ).
1166  arg( -(mPipeRc) ).arg( cmd ) );
1167  } else {
1168  KMessageBox::detailedError( 0,
1169  i18n( "Pipe command killed by signal %1: %2" ).
1170  arg( -(mPipeRc) ).arg( cmd ), mPipeErr );
1171  }
1172  }
1173  }
1174 
1175  } else {
1176  // process does not exited after TemplateParser::PipeTimeout seconds, kill it
1177  proc.kill();
1178  proc.detach();
1179  if ( mDebug ) {
1180  KMessageBox::error( 0,
1181  i18n( "Pipe command did not finish within %1 seconds: %2" ).
1182  arg( PipeTimeout ).arg( cmd ) );
1183  }
1184  }
1185 
1186  } else {
1187  // can`t write to stdin of process
1188  proc.kill();
1189  proc.detach();
1190  if ( mDebug ) {
1191  if ( mPipeErr.isEmpty() ) {
1192  KMessageBox::error( 0,
1193  i18n( "Cannot write to process stdin: %1" ).arg( cmd ) );
1194  } else {
1195  KMessageBox::detailedError( 0,
1196  i18n( "Cannot write to process stdin: %1" ).
1197  arg( cmd ), mPipeErr );
1198  }
1199  }
1200  }
1201 
1202  } else if ( mDebug ) {
1203  KMessageBox::error( 0,
1204  i18n( "Cannot start pipe command from template: %1" ).
1205  arg( cmd ) );
1206  }
1207 
1208  return mPipeOut;
1209 }
1210 
1211 void TemplateParser::onProcessExited( TDEProcess *proc )
1212 {
1213  Q_UNUSED( proc );
1214  // do nothing for now
1215 }
1216 
1217 void TemplateParser::onReceivedStdout( TDEProcess *proc, char *buffer, int buflen )
1218 {
1219  Q_UNUSED( proc );
1220  mPipeOut += TQString::fromLocal8Bit( buffer, buflen );
1221 }
1222 
1223 void TemplateParser::onReceivedStderr( TDEProcess *proc, char *buffer, int buflen )
1224 {
1225  Q_UNUSED( proc );
1226  mPipeErr += TQString::fromLocal8Bit( buffer, buflen );
1227 }
1228 
1229 void TemplateParser::onWroteStdin( TDEProcess *proc )
1230 {
1231  proc->closeStdin();
1232 }
1233 
1234 #include "templateparser.moc"
Mail folder.
Definition: kmfolder.h:69
TQString idString() const
Returns a string that can be used to identify this folder.
Definition: kmfolder.cpp:705
This is a Mime Message.
Definition: kmmessage.h:68
uint identityUoid() const
Definition: kmmessage.cpp:1727
void setBody(const TQCString &aStr)
Set the message body.
Definition: kmmessage.cpp:2774
void setBodyFromUnicode(const TQString &str, DwEntity *entity=0)
Sets this body's content to str.
Definition: kmmessage.cpp:4440
TQCString body() const
Get the message body.
Definition: kmmessage.cpp:2570
TQString from() const
Get or set the 'From' header field.
Definition: kmmessage.cpp:2015
TQString asQuotedString(const TQString &headerStr, const TQString &indentStr, const TQString &selection=TQString(), bool aStripSignature=true, bool allowDecryption=true) const
Returns message body with quoting header and indented by the given indentation string.
Definition: kmmessage.cpp:835
TQString to() const
Get or set the 'To' header field.
Definition: kmmessage.cpp:1894
TQString subject() const
Get or set the 'Subject' header field.
Definition: kmmessage.cpp:2049
TQCString headerAsSendableString() const
Return the message header with the headers that should not be sent stripped off.
Definition: kmmessage.cpp:327
void addDwBodyPart(DwBodyPart *aDwPart)
Append a DwBodyPart to the message.
Definition: kmmessage.cpp:3348
TQStringList headerFields(const TQCString &name) const
Returns a list of the values of all header fields with the given name.
Definition: kmmessage.cpp:2302
TQString asPlainTextFromObjectTree(partNode *root, bool stripSignature, bool allowDecryption) const
Same as asPlainText(), only that this method expects an already parsed object tree as paramter.
Definition: kmmessage.cpp:740
TQCString asString() const
Return the entire message contents as a string.
Definition: kmmessage.cpp:314
TQString cc() const
Get or set the 'Cc' header field.
Definition: kmmessage.cpp:1940
TQCString id() const
Returns the message ID, useful for followups.
Definition: kmmessage.cpp:208
DwHeaders & headers() const
get the DwHeaders (make sure to call setNeedsAssembly() function after directly modyfying internal da...
Definition: kmmessage.cpp:2546
void assembleIfNeeded()
Assemble the internal message.
Definition: kmmessage.cpp:2559
void deleteBodyParts()
Delete all body parts.
Definition: kmmessage.cpp:3174
DwBodyPart * createDWBodyPart(const KMMessagePart *aPart)
Compose a DwBodyPart (needed for adding a part to the message).
Definition: kmmessage.cpp:3218
void setCursorPos(int pos)
Set cursor position as offset from message start.
Definition: kmmessage.h:927
void setAllowDecryption(const bool allowDecryption)
Sets whether the template parser is allowed to decrypt the original message when needing its message ...
virtual TQString findCustomTemplate(const TQString &tmpl)
Finds the template with the given name.
void setSelection(const TQString &selection)
Sets the selection.
bool shouldStripSignature() const
Determines whether the signature should be stripped when getting the text of the original message,...
virtual TQString findTemplate()
This finds the template to use.
partNode * parsedObjectTree()
Returns the parsed object tree of the original message.
TQString messageText(bool allowSelectionOnly)
If there was a text selection set in the constructor, that will be returned.
void addProcessedBodyToMessage(const TQString &body)
Called by processWithTemplate().
void append(TQByteArray &that, const TQByteArray &str)
Append a bytearray to a bytearray.
Definition: util.cpp:144
folderdiaquotatab.h
Definition: aboutdata.cpp:40