• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.9.4 API Reference
  • KDE Home
  • Contact Us
 

kpimidentities

  • kpimidentities
signature.cpp
1 /*
2  Copyright (c) 2002-2004 Marc Mutz <mutz@kde.org>
3  Copyright (c) 2007 Tom Albers <tomalbers@kde.nl>
4  Copyright (c) 2009 Thomas McGuire <mcguire@kde.org>
5 
6  This library is free software; you can redistribute it and/or modify it
7  under the terms of the GNU Library General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or (at your
9  option) any later version.
10 
11  This library is distributed in the hope that it will be useful, but WITHOUT
12  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14  License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to the
18  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  02110-1301, USA.
20 */
21 
22 #include "signature.h"
23 
24 #include <kdebug.h>
25 #include <klocale.h>
26 #include <kmessagebox.h>
27 #include <kconfiggroup.h>
28 #include <kurl.h>
29 #include <kprocess.h>
30 #include <KRichTextEdit>
31 #include <kpimutils/kfileio.h>
32 
33 #include <QFileInfo>
34 #include <QSharedPointer>
35 #include <QImage>
36 
37 #include <assert.h>
38 #include <QtCore/QDir>
39 #include <kpimtextedit/textedit.h>
40 
41 using namespace KPIMIdentities;
42 
43 class SignaturePrivate
44 {
45  public:
46  SignaturePrivate()
47  :enabled(false)
48  {
49  }
50  struct EmbeddedImage
51  {
52  QImage image;
53  QString name;
54  };
55  typedef QSharedPointer<EmbeddedImage> EmbeddedImagePtr;
56 
59  QList<EmbeddedImagePtr> embeddedImages;
60 
62  QString saveLocation;
63  bool enabled;
64 };
65 
66 QDataStream &operator<< ( QDataStream &stream, const SignaturePrivate::EmbeddedImagePtr &img )
67 {
68  return stream << img->image << img->name;
69 }
70 
71 QDataStream &operator>> ( QDataStream &stream, SignaturePrivate::EmbeddedImagePtr &img )
72 {
73  return stream >> img->image >> img->name;
74 }
75 
76 // TODO: KDE5: BIC: Add a real d-pointer.
77 // This QHash is just a workaround around BIC issues, for more info see
78 // http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++
79 typedef QHash<const Signature*,SignaturePrivate*> SigPrivateHash;
80 Q_GLOBAL_STATIC(SigPrivateHash, d_func)
81 
82 static SignaturePrivate* d( const Signature *sig )
83 {
84  SignaturePrivate *ret = d_func()->value( sig, 0 );
85  if ( !ret ) {
86  ret = new SignaturePrivate;
87  d_func()->insert( sig, ret );
88  }
89  return ret;
90 }
91 
92 static void delete_d( const Signature* sig )
93 {
94  SignaturePrivate *ret = d_func()->value( sig, 0 );
95  delete ret;
96  d_func()->remove( sig );
97 }
98 
99 Signature::Signature()
100  : mType( Disabled ),
101  mInlinedHtml( false )
102 {}
103 
104 Signature::Signature( const QString &text )
105  : mText( text ),
106  mType( Inlined ),
107  mInlinedHtml( false )
108 {}
109 
110 Signature::Signature( const QString &url, bool isExecutable )
111  : mUrl( url ),
112  mType( isExecutable ? FromCommand : FromFile ),
113  mInlinedHtml( false )
114 {}
115 
116 void Signature::assignFrom ( const KPIMIdentities::Signature &that )
117 {
118  mUrl = that.mUrl;
119  mInlinedHtml = that.mInlinedHtml;
120  mText = that.mText;
121  mType = that.mType;
122  d( this )->enabled = d( &that )->enabled;
123  d( this )->saveLocation = d( &that )->saveLocation;
124  d( this )->embeddedImages = d( &that )->embeddedImages;
125 }
126 
127 Signature::Signature ( const Signature &that )
128 {
129  assignFrom( that );
130 }
131 
132 Signature& Signature::operator= ( const KPIMIdentities::Signature & that )
133 {
134  if ( this == &that )
135  return *this;
136 
137  assignFrom( that );
138  return *this;
139 }
140 
141 Signature::~Signature()
142 {
143  delete_d( this );
144 }
145 
146 QString Signature::rawText( bool *ok ) const
147 {
148  switch ( mType ) {
149  case Disabled:
150  if ( ok ) {
151  *ok = true;
152  }
153  return QString();
154  case Inlined:
155  if ( ok ) {
156  *ok = true;
157  }
158  return mText;
159  case FromFile:
160  return textFromFile( ok );
161  case FromCommand:
162  return textFromCommand( ok );
163  };
164  kFatal(5325) << "Signature::type() returned unknown value!";
165  return QString(); // make compiler happy
166 }
167 
168 QString Signature::textFromCommand( bool *ok ) const
169 {
170  assert( mType == FromCommand );
171 
172  // handle pathological cases:
173  if ( mUrl.isEmpty() ) {
174  if ( ok ) {
175  *ok = true;
176  }
177  return QString();
178  }
179 
180  // create a shell process:
181  KProcess proc;
182  proc.setOutputChannelMode( KProcess::SeparateChannels );
183  proc.setShellCommand( mUrl );
184  int rc = proc.execute();
185 
186  // handle errors, if any:
187  if ( rc != 0 ) {
188  if ( ok ) {
189  *ok = false;
190  }
191  QString wmsg = i18n( "<qt>Failed to execute signature script<p><b>%1</b>:</p>"
192  "<p>%2</p></qt>", mUrl, QString( proc.readAllStandardError() ) );
193  KMessageBox::error( 0, wmsg );
194  return QString();
195  }
196 
197  // no errors:
198  if ( ok ) {
199  *ok = true;
200  }
201 
202  // get output:
203  QByteArray output = proc.readAllStandardOutput();
204 
205  // TODO: hmm, should we allow other encodings, too?
206  return QString::fromLocal8Bit( output.data(), output.size() );
207 }
208 
209 QString Signature::textFromFile( bool *ok ) const
210 {
211  assert( mType == FromFile );
212 
213  // TODO: Use KIO::NetAccess to download non-local files!
214  if ( !KUrl( mUrl ).isLocalFile() &&
215  !( QFileInfo( mUrl ).isRelative() &&
216  QFileInfo( mUrl ).exists() ) ) {
217  kDebug(5325) << "Signature::textFromFile:"
218  << "non-local URLs are unsupported";
219  if ( ok ) {
220  *ok = false;
221  }
222  return QString();
223  }
224 
225  if ( ok ) {
226  *ok = true;
227  }
228 
229  // TODO: hmm, should we allow other encodings, too?
230  const QByteArray ba = KPIMUtils::kFileToByteArray( mUrl, false );
231  return QString::fromLocal8Bit( ba.data(), ba.size() );
232 }
233 
234 QString Signature::withSeparator( bool *ok ) const
235 {
236  QString signature = rawText( ok );
237  if ( ok && (*ok) == false )
238  return QString();
239 
240  if ( signature.isEmpty() ) {
241  return signature; // don't add a separator in this case
242  }
243 
244  const bool htmlSig = ( isInlinedHtml() && mType == Inlined );
245  QString newline = htmlSig ? "<br>" : "\n";
246  if ( htmlSig && signature.startsWith( QLatin1String( "<p" ) ) ) {
247  newline.clear();
248  }
249 
250  if ( signature.startsWith( QString::fromLatin1( "-- " ) + newline )
251  || ( signature.indexOf( newline + QString::fromLatin1( "-- " ) +
252  newline ) != -1 ) ) {
253  // already have signature separator at start of sig or inside sig:
254  return signature;
255  } else {
256  // need to prepend one:
257  return QString::fromLatin1( "-- " ) + newline + signature;
258  }
259 }
260 
261 void Signature::setUrl( const QString &url, bool isExecutable )
262 {
263  mUrl = url;
264  mType = isExecutable ? FromCommand : FromFile;
265 }
266 
267 void Signature::setInlinedHtml( bool isHtml )
268 {
269  mInlinedHtml = isHtml;
270 }
271 
272 bool Signature::isInlinedHtml() const
273 {
274  return mInlinedHtml;
275 }
276 
277 // config keys and values:
278 static const char sigTypeKey[] = "Signature Type";
279 static const char sigTypeInlineValue[] = "inline";
280 static const char sigTypeFileValue[] = "file";
281 static const char sigTypeCommandValue[] = "command";
282 static const char sigTypeDisabledValue[] = "disabled";
283 static const char sigTextKey[] = "Inline Signature";
284 static const char sigFileKey[] = "Signature File";
285 static const char sigCommandKey[] = "Signature Command";
286 static const char sigTypeInlinedHtmlKey[] = "Inlined Html";
287 static const char sigImageLocation[] = "Image Location";
288 static const char sigEnabled[] ="Signature Enabled";
289 
290 // Returns the names of all images in the HTML code
291 static QStringList findImageNames( const QString &htmlCode )
292 {
293  QStringList ret;
294 
295  // To complicated for us, so cheat and let a text edit do the hard work
296  KPIMTextEdit::TextEdit edit;
297  edit.setHtml( htmlCode );
298  foreach( const KPIMTextEdit::ImageWithNamePtr &image, edit.imagesWithName() ) {
299  ret << image->name;
300  }
301  return ret;
302 }
303 
304 void Signature::cleanupImages() const
305 {
306  // Remove any images from the internal structure that are no longer there
307  if ( isInlinedHtml() ) {
308  foreach( const SignaturePrivate::EmbeddedImagePtr &imageInList, d( this )->embeddedImages ) {
309  bool found = false;
310  foreach( const QString &imageInHtml, findImageNames( mText ) ) {
311  if ( imageInHtml == imageInList->name ) {
312  found = true;
313  break;
314  }
315  }
316  if ( !found )
317  d( this )->embeddedImages.removeAll( imageInList );
318  }
319  }
320 
321  // Delete all the old image files
322  if ( !d( this )->saveLocation.isEmpty() ) {
323  QDir dir( d( this )->saveLocation );
324  foreach( const QString &fileName, dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ) ) {
325  if ( fileName.toLower().endsWith( QLatin1String( ".png" ) ) ) {
326  kDebug() << "Deleting old image" << dir.path() + fileName;
327  dir.remove( fileName );
328  }
329  }
330  }
331 }
332 
333 void Signature::saveImages() const
334 {
335  if ( isInlinedHtml() && !d( this )->saveLocation.isEmpty() ) {
336  foreach( const SignaturePrivate::EmbeddedImagePtr &image, d( this )->embeddedImages ) {
337  QString location = d( this )->saveLocation + '/' + image->name;
338  if ( !image->image.save( location, "PNG" ) ) {
339  kWarning() << "Failed to save image" << location;
340  }
341  }
342  }
343 }
344 
345 void Signature::readConfig( const KConfigGroup &config )
346 {
347  QString sigType = config.readEntry( sigTypeKey );
348  if ( sigType == sigTypeInlineValue ) {
349  mType = Inlined;
350  mInlinedHtml = config.readEntry( sigTypeInlinedHtmlKey, false );
351  } else if ( sigType == sigTypeFileValue ) {
352  mType = FromFile;
353  mUrl = config.readPathEntry( sigFileKey, QString() );
354  } else if ( sigType == sigTypeCommandValue ) {
355  mType = FromCommand;
356  mUrl = config.readPathEntry( sigCommandKey, QString() );
357  } else if ( sigType == sigTypeDisabledValue ) {
358  d(this)->enabled = false;
359  }
360  if(mType!=Disabled)
361  d(this)->enabled = config.readEntry(sigEnabled,true);
362 
363  mText = config.readEntry( sigTextKey );
364  d( this )->saveLocation = config.readEntry( sigImageLocation );
365 
366  if ( isInlinedHtml() && !d( this )->saveLocation.isEmpty() ) {
367  QDir dir( d( this )->saveLocation );
368  foreach( const QString &fileName, dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ) ) {
369  if ( fileName.toLower().endsWith( QLatin1String( ".png" ) ) ) {
370  QImage image;
371  if ( image.load( dir.path() + '/' + fileName ) ) {
372  addImage( image, fileName );
373  }
374  else {
375  kWarning() << "Unable to load image" << dir.path() + '/' + fileName;
376  }
377  }
378  }
379  }
380 }
381 
382 void Signature::writeConfig( KConfigGroup &config ) const
383 {
384  switch ( mType ) {
385  case Inlined:
386  config.writeEntry( sigTypeKey, sigTypeInlineValue );
387  config.writeEntry( sigTypeInlinedHtmlKey, mInlinedHtml );
388  break;
389  case FromFile:
390  config.writeEntry( sigTypeKey, sigTypeFileValue );
391  config.writePathEntry( sigFileKey, mUrl );
392  break;
393  case FromCommand:
394  config.writeEntry( sigTypeKey, sigTypeCommandValue );
395  config.writePathEntry( sigCommandKey, mUrl );
396  break;
397  default:
398  break;
399  }
400  config.writeEntry( sigTextKey, mText );
401  config.writeEntry( sigImageLocation, d( this )->saveLocation );
402  config.writeEntry( sigEnabled, d( this )->enabled );
403 
404  cleanupImages();
405  saveImages();
406 }
407 
408 static bool isCursorAtEndOfLine( const QTextCursor &cursor )
409 {
410  QTextCursor testCursor = cursor;
411  testCursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor );
412  return !testCursor.hasSelection();
413 }
414 
415 static void insertSignatureHelper( const QString &signature,
416  KRichTextEdit *textEdit,
417  Signature::Placement placement,
418  bool isHtml,
419  bool addNewlines )
420 {
421  if ( !signature.isEmpty() ) {
422 
423  // Save the modified state of the document, as inserting a signature
424  // shouldn't change this. Restore it at the end of this function.
425  bool isModified = textEdit->document()->isModified();
426 
427  // Move to the desired position, where the signature should be inserted
428  QTextCursor cursor = textEdit->textCursor();
429  QTextCursor oldCursor = cursor;
430  cursor.beginEditBlock();
431 
432  if ( placement == Signature::End )
433  cursor.movePosition( QTextCursor::End );
434  else if ( placement == Signature::Start )
435  cursor.movePosition( QTextCursor::Start );
436  else if ( placement == Signature::AtCursor )
437  cursor.movePosition( QTextCursor::StartOfLine );
438  textEdit->setTextCursor( cursor );
439 
440 
441  QString lineSep;
442  if ( addNewlines ) {
443  if ( isHtml )
444  lineSep = QLatin1String( "<br>" );
445  else
446  lineSep = QLatin1Char( '\n' );
447  }
448 
449  // Insert the signature and newlines depending on where it was inserted.
450  bool hackForCursorsAtEnd = false;
451  int oldCursorPos = -1;
452  if ( placement == Signature::End ) {
453 
454  if ( oldCursor.position() == textEdit->toPlainText().length() ) {
455  hackForCursorsAtEnd = true;
456  oldCursorPos = oldCursor.position();
457  }
458  if ( isHtml ) {
459  textEdit->insertHtml( lineSep + signature );
460  } else {
461  textEdit->insertPlainText( lineSep + signature );
462  }
463  } else if ( placement == Signature::Start || placement == Signature::AtCursor ) {
464  if ( isHtml ) {
465  if ( isCursorAtEndOfLine( cursor ) )
466  textEdit->insertHtml( signature );
467  else
468  textEdit->insertHtml( signature + lineSep );
469  } else {
470  if ( isCursorAtEndOfLine( cursor ) )
471  textEdit->insertPlainText( signature );
472  else
473  textEdit->insertPlainText( signature + lineSep );
474  }
475  }
476 
477  cursor.endEditBlock();
478 
479  // There is one special case when re-setting the old cursor: The cursor
480  // was at the end. In this case, QTextEdit has no way to know
481  // if the signature was added before or after the cursor, and just decides
482  // that it was added before (and the cursor moves to the end, but it should
483  // not when appending a signature). See bug 167961
484  if ( hackForCursorsAtEnd )
485  oldCursor.setPosition( oldCursorPos );
486 
487  textEdit->setTextCursor( oldCursor );
488  textEdit->ensureCursorVisible();
489 
490  textEdit->document()->setModified( isModified );
491 
492  if ( isHtml ) {
493  textEdit->enableRichTextMode();
494  }
495  }
496 }
497 
498 void Signature::insertIntoTextEdit( KRichTextEdit *textEdit,
499  Placement placement, bool addSeparator )
500 {
501  if(!isEnabledSignature()) {
502  return;
503  }
504  QString signature;
505  if ( addSeparator )
506  signature = withSeparator();
507  else
508  signature = rawText();
509  insertSignatureHelper( signature, textEdit, placement,
510  ( isInlinedHtml() &&
511  type() == KPIMIdentities::Signature::Inlined ),
512  true );
513 }
514 
515 void Signature::insertIntoTextEdit( Placement placement, AddedText addedText,
516  KPIMTextEdit::TextEdit *textEdit ) const
517 {
518  insertSignatureText(placement,addedText, textEdit, false);
519 }
520 
521 void Signature::insertIntoTextEdit( Placement placement, AddedText addedText,
522  KPIMTextEdit::TextEdit *textEdit, bool forceDisplay ) const
523 {
524  insertSignatureText(placement,addedText, textEdit, forceDisplay);
525 }
526 
527 void Signature::insertSignatureText(Placement placement, AddedText addedText, KPIMTextEdit::TextEdit *textEdit, bool forceDisplay) const
528 {
529  if(!forceDisplay) {
530  if(!isEnabledSignature()) {
531  return;
532  }
533  }
534  QString signature;
535  if ( addedText & AddSeparator )
536  signature = withSeparator();
537  else
538  signature = rawText();
539  insertSignatureHelper( signature, textEdit, placement,
540  ( isInlinedHtml() &&
541  type() == KPIMIdentities::Signature::Inlined ),
542  ( addedText & AddNewLines ) );
543 
544  // We added the text of the signature above, now it is time to add the images as well.
545  if ( isInlinedHtml() ) {
546  foreach( const SignaturePrivate::EmbeddedImagePtr &image, d( this )->embeddedImages ) {
547  textEdit->loadImage( image->image, image->name, image->name );
548  }
549  }
550 }
551 
552 
553 void Signature::insertPlainSignatureIntoTextEdit( const QString &signature, KRichTextEdit *textEdit,
554  Signature::Placement placement, bool isHtml )
555 {
556  insertSignatureHelper( signature, textEdit, placement, isHtml, true );
557 }
558 
559 // --------------------- Operators -------------------//
560 
561 QDataStream &KPIMIdentities::operator<<
562 ( QDataStream &stream, const KPIMIdentities::Signature &sig )
563 {
564  return stream << static_cast<quint8>( sig.mType ) << sig.mUrl << sig.mText
565  << d( &sig )->saveLocation << d( &sig )->embeddedImages<<d( &sig )->enabled;
566 }
567 
568 QDataStream &KPIMIdentities::operator>>
569 ( QDataStream &stream, KPIMIdentities::Signature &sig )
570 {
571  quint8 s;
572  stream >> s >> sig.mUrl >> sig.mText >> d( &sig )->saveLocation >> d( &sig )->embeddedImages >>d( &sig )->enabled;
573  sig.mType = static_cast<Signature::Type>( s );
574  return stream;
575 }
576 
577 bool Signature::operator== ( const Signature &other ) const
578 {
579  if ( mType != other.mType ) {
580  return false;
581  }
582 
583  if( d( this )->enabled != d( &other )->enabled )
584  return false;
585 
586  if ( mType == Inlined && mInlinedHtml ) {
587  if ( d( this )->saveLocation != d( &other )->saveLocation )
588  return false;
589  if ( d( this )->embeddedImages != d( &other )->embeddedImages )
590  return false;
591  }
592 
593  switch ( mType ) {
594  case Inlined:
595  return mText == other.mText;
596  case FromFile:
597  case FromCommand:
598  return mUrl == other.mUrl;
599  default:
600  case Disabled:
601  return true;
602  }
603 }
604 
605 QString Signature::toPlainText() const
606 {
607  QString sigText = rawText();
608  if ( isInlinedHtml() && type() == Inlined ) {
609  // Use a QTextDocument as a helper, it does all the work for us and
610  // strips all HTML tags.
611  QTextDocument helper;
612  QTextCursor helperCursor( &helper );
613  helperCursor.insertHtml( sigText );
614  sigText = helper.toPlainText();
615  }
616  return sigText;
617 }
618 
619 void Signature::addImage ( const QImage& imageData, const QString& imageName )
620 {
621  Q_ASSERT( !( d( this )->saveLocation.isEmpty() ) );
622  SignaturePrivate::EmbeddedImagePtr image( new SignaturePrivate::EmbeddedImage() );
623  image->image = imageData;
624  image->name = imageName;
625  d( this )->embeddedImages.append( image );
626 }
627 
628 void Signature::setImageLocation ( const QString& path )
629 {
630  d( this )->saveLocation = path;
631 }
632 
633 // --------------- Getters -----------------------//
634 
635 QString Signature::text() const
636 {
637  return mText;
638 }
639 
640 QString Signature::url() const
641 {
642  return mUrl;
643 }
644 
645 Signature::Type Signature::type() const
646 {
647  return mType;
648 }
649 
650 // --------------- Setters -----------------------//
651 
652 void Signature::setText( const QString &text )
653 {
654  mText = text;
655  mType = Inlined;
656 }
657 
658 void Signature::setType( Type type )
659 {
660  mType = type;
661 }
662 
663 
664 void Signature::setEnabledSignature(bool enabled)
665 {
666  d( this )->enabled = enabled;
667 }
668 
669 bool Signature::isEnabledSignature() const
670 {
671  return d( this )->enabled;
672 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Mon Dec 10 2012 13:49:18 by doxygen 1.8.1.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

kpimidentities

Skip menu "kpimidentities"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.9.4 API Reference

Skip menu "kdepimlibs-4.9.4 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal