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

kpimidentities

signature.cpp
00001 /*
00002     Copyright (c) 2002-2004 Marc Mutz <mutz@kde.org>
00003     Copyright (c) 2007 Tom Albers <tomalbers@kde.nl>
00004     Copyright (c) 2009 Thomas McGuire <mcguire@kde.org>
00005 
00006     This library is free software; you can redistribute it and/or modify it
00007     under the terms of the GNU Library General Public License as published by
00008     the Free Software Foundation; either version 2 of the License, or (at your
00009     option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful, but WITHOUT
00012     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00013     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
00014     License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to the
00018     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00019     02110-1301, USA.
00020 */
00021 
00022 #include "signature.h"
00023 
00024 #include <kdebug.h>
00025 #include <klocale.h>
00026 #include <kmessagebox.h>
00027 #include <kconfiggroup.h>
00028 #include <kurl.h>
00029 #include <kprocess.h>
00030 #include <KRichTextEdit>
00031 #include <kpimutils/kfileio.h>
00032 
00033 #include <QFileInfo>
00034 #include <QSharedPointer>
00035 #include <QImage>
00036 
00037 #include <assert.h>
00038 #include <QtCore/QDir>
00039 #include <kpimtextedit/textedit.h>
00040 
00041 using namespace KPIMIdentities;
00042 
00043 class SignaturePrivate
00044 {
00045   public:
00046     struct EmbeddedImage
00047     {
00048       QImage image;
00049       QString name;
00050     };
00051     typedef QSharedPointer<EmbeddedImage> EmbeddedImagePtr;
00052 
00055     QList<EmbeddedImagePtr> embeddedImages;
00056 
00058     QString saveLocation;
00059 };
00060 
00061 QDataStream &operator<< ( QDataStream &stream, const SignaturePrivate::EmbeddedImagePtr &img )
00062 {
00063   return stream << img->image << img->name;
00064 }
00065 
00066 QDataStream &operator>> ( QDataStream &stream, SignaturePrivate::EmbeddedImagePtr &img )
00067 {
00068   return stream >> img->image >> img->name;
00069 }
00070 
00071 // TODO: KDE5: BIC: Add a real d-pointer.
00072 // This QHash is just a workaround around BIC issues, for more info see
00073 // http://techbase.kde.org/Policies/Binary_Compatibility_Issues_With_C++
00074 typedef QHash<const Signature*,SignaturePrivate*> SigPrivateHash;
00075 Q_GLOBAL_STATIC(SigPrivateHash, d_func)
00076 
00077 static SignaturePrivate* d( const Signature *sig )
00078 {
00079   SignaturePrivate *ret = d_func()->value( sig, 0 );
00080   if ( !ret ) {
00081     ret = new SignaturePrivate;
00082     d_func()->insert( sig, ret );
00083   }
00084   return ret;
00085 }
00086 
00087 static void delete_d( const Signature* sig )
00088 {
00089   SignaturePrivate *ret = d_func()->value( sig, 0 );
00090   delete ret;
00091   d_func()->remove( sig );
00092 }
00093 
00094 Signature::Signature()
00095   : mType( Disabled ),
00096     mInlinedHtml( false )
00097 {}
00098 
00099 Signature::Signature( const QString &text )
00100   : mText( text ),
00101     mType( Inlined ),
00102     mInlinedHtml( false )
00103 {}
00104 
00105 Signature::Signature( const QString &url, bool isExecutable )
00106   : mUrl( url ),
00107     mType( isExecutable ? FromCommand : FromFile ),
00108     mInlinedHtml( false )
00109 {}
00110 
00111 void Signature::assignFrom ( const KPIMIdentities::Signature &that )
00112 {
00113   mUrl = that.mUrl;
00114   mInlinedHtml = that.mInlinedHtml;
00115   mText = that.mText;
00116   mType = that.mType;
00117   d( this )->saveLocation = d( &that )->saveLocation;
00118   d( this )->embeddedImages = d( &that )->embeddedImages;
00119 }
00120 
00121 Signature::Signature ( const Signature &that )
00122 {
00123   assignFrom( that );
00124 }
00125 
00126 Signature& Signature::operator= ( const KPIMIdentities::Signature & that )
00127 {
00128   if ( this == &that )
00129     return *this;
00130 
00131   assignFrom( that );
00132   return *this;
00133 }
00134 
00135 Signature::~Signature()
00136 {
00137   delete_d( this );
00138 }
00139 
00140 QString Signature::rawText( bool *ok ) const
00141 {
00142   switch ( mType ) {
00143   case Disabled:
00144     if ( ok ) {
00145       *ok = true;
00146     }
00147     return QString();
00148   case Inlined:
00149     if ( ok ) {
00150       *ok = true;
00151     }
00152     return mText;
00153   case FromFile:
00154     return textFromFile( ok );
00155   case FromCommand:
00156     return textFromCommand( ok );
00157   };
00158   kFatal(5325) << "Signature::type() returned unknown value!";
00159   return QString(); // make compiler happy
00160 }
00161 
00162 QString Signature::textFromCommand( bool *ok ) const
00163 {
00164   assert( mType == FromCommand );
00165 
00166   // handle pathological cases:
00167   if ( mUrl.isEmpty() ) {
00168     if ( ok ) {
00169       *ok = true;
00170     }
00171     return QString();
00172   }
00173 
00174   // create a shell process:
00175   KProcess proc;
00176   proc.setOutputChannelMode( KProcess::SeparateChannels );
00177   proc.setShellCommand( mUrl );
00178   int rc = proc.execute();
00179 
00180   // handle errors, if any:
00181   if ( rc != 0 ) {
00182     if ( ok ) {
00183       *ok = false;
00184     }
00185     QString wmsg = i18n( "<qt>Failed to execute signature script<p><b>%1</b>:</p>"
00186                          "<p>%2</p></qt>", mUrl, QString( proc.readAllStandardError() ) );
00187     KMessageBox::error( 0, wmsg );
00188     return QString();
00189   }
00190 
00191   // no errors:
00192   if ( ok ) {
00193     *ok = true;
00194   }
00195 
00196   // get output:
00197   QByteArray output = proc.readAllStandardOutput();
00198 
00199   // TODO: hmm, should we allow other encodings, too?
00200   return QString::fromLocal8Bit( output.data(), output.size() );
00201 }
00202 
00203 QString Signature::textFromFile( bool *ok ) const
00204 {
00205   assert( mType == FromFile );
00206 
00207   // TODO: Use KIO::NetAccess to download non-local files!
00208   if ( !KUrl( mUrl ).isLocalFile() &&
00209        !( QFileInfo( mUrl ).isRelative() &&
00210           QFileInfo( mUrl ).exists() ) ) {
00211     kDebug(5325) << "Signature::textFromFile:"
00212     << "non-local URLs are unsupported";
00213     if ( ok ) {
00214       *ok = false;
00215     }
00216     return QString();
00217   }
00218 
00219   if ( ok ) {
00220     *ok = true;
00221   }
00222 
00223   // TODO: hmm, should we allow other encodings, too?
00224   const QByteArray ba = KPIMUtils::kFileToByteArray( mUrl, false );
00225   return QString::fromLocal8Bit( ba.data(), ba.size() );
00226 }
00227 
00228 QString Signature::withSeparator( bool *ok ) const
00229 {
00230   QString signature = rawText( ok );
00231   if ( ok && (*ok) == false )
00232     return QString();
00233 
00234   if ( signature.isEmpty() ) {
00235     return signature; // don't add a separator in this case
00236   }
00237 
00238   const bool htmlSig = ( isInlinedHtml() && mType == Inlined );
00239   QString newline = htmlSig ? "<br>" : "\n";
00240   if ( htmlSig && signature.startsWith( QLatin1String( "<p" ) ) ) {
00241     newline.clear();
00242   }
00243 
00244   if ( signature.startsWith( QString::fromLatin1( "-- " ) + newline )
00245     || ( signature.indexOf( newline + QString::fromLatin1( "-- " ) +
00246                             newline ) != -1 ) ) {
00247     // already have signature separator at start of sig or inside sig:
00248     return signature;
00249   } else {
00250     // need to prepend one:
00251     return QString::fromLatin1( "-- " ) + newline + signature;
00252   }
00253 }
00254 
00255 void Signature::setUrl( const QString &url, bool isExecutable )
00256 {
00257   mUrl = url;
00258   mType = isExecutable ? FromCommand : FromFile;
00259 }
00260 
00261 void Signature::setInlinedHtml( bool isHtml )
00262 {
00263   mInlinedHtml = isHtml;
00264 }
00265 
00266 bool Signature::isInlinedHtml() const
00267 {
00268   return mInlinedHtml;
00269 }
00270 
00271 // config keys and values:
00272 static const char sigTypeKey[] = "Signature Type";
00273 static const char sigTypeInlineValue[] = "inline";
00274 static const char sigTypeFileValue[] = "file";
00275 static const char sigTypeCommandValue[] = "command";
00276 static const char sigTypeDisabledValue[] = "disabled";
00277 static const char sigTextKey[] = "Inline Signature";
00278 static const char sigFileKey[] = "Signature File";
00279 static const char sigCommandKey[] = "Signature Command";
00280 static const char sigTypeInlinedHtmlKey[] = "Inlined Html";
00281 static const char sigImageLocation[] = "Image Location";
00282 
00283 // Returns the names of all images in the HTML code
00284 static QStringList findImageNames( const QString &htmlCode )
00285 {
00286   QStringList ret;
00287 
00288   // To complicated for us, so cheat and let a text edit do the hard work
00289   KPIMTextEdit::TextEdit edit;
00290   edit.setHtml( htmlCode );
00291   foreach( const KPIMTextEdit::ImageWithNamePtr &image, edit.imagesWithName() ) {
00292     ret << image->name;
00293   }
00294   return ret;
00295 }
00296 
00297 void Signature::cleanupImages() const
00298 {
00299   // Remove any images from the internal structure that are no longer there
00300   if ( isInlinedHtml() ) {
00301     foreach( const SignaturePrivate::EmbeddedImagePtr &imageInList, d( this )->embeddedImages ) {
00302       bool found = false;
00303       foreach( const QString &imageInHtml, findImageNames( mText ) ) {
00304         if ( imageInHtml == imageInList->name ) {
00305           found = true;
00306           break;
00307         }
00308       }
00309       if ( !found )
00310         d( this )->embeddedImages.removeAll( imageInList );
00311     }
00312   }
00313 
00314   // Delete all the old image files
00315   if ( !d( this )->saveLocation.isEmpty() ) {
00316     QDir dir( d( this )->saveLocation );
00317     foreach( const QString &fileName, dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ) ) {
00318       if ( fileName.toLower().endsWith( QLatin1String( ".png" ) ) ) {
00319         kDebug() << "Deleting old image" << dir.path() + fileName;
00320         dir.remove( fileName );
00321       }
00322     }
00323   }
00324 }
00325 
00326 void Signature::saveImages() const
00327 {
00328   if ( isInlinedHtml() && !d( this )->saveLocation.isEmpty() ) {
00329     foreach( const SignaturePrivate::EmbeddedImagePtr &image, d( this )->embeddedImages ) {
00330       QString location = d( this )->saveLocation + '/' + image->name;
00331       if ( !image->image.save( location, "PNG" ) ) {
00332         kWarning() << "Failed to save image" << location;
00333       }
00334     }
00335   }
00336 }
00337 
00338 void Signature::readConfig( const KConfigGroup &config )
00339 {
00340   QString sigType = config.readEntry( sigTypeKey );
00341   if ( sigType == sigTypeInlineValue ) {
00342     mType = Inlined;
00343     mInlinedHtml = config.readEntry( sigTypeInlinedHtmlKey, false );
00344   } else if ( sigType == sigTypeFileValue ) {
00345     mType = FromFile;
00346     mUrl = config.readPathEntry( sigFileKey, QString() );
00347   } else if ( sigType == sigTypeCommandValue ) {
00348     mType = FromCommand;
00349     mUrl = config.readPathEntry( sigCommandKey, QString() );
00350   } else {
00351     mType = Disabled;
00352   }
00353   mText = config.readEntry( sigTextKey );
00354   d( this )->saveLocation = config.readEntry( sigImageLocation );
00355 
00356   if ( isInlinedHtml() && !d( this )->saveLocation.isEmpty() ) {
00357     QDir dir( d( this )->saveLocation );
00358     foreach( const QString &fileName, dir.entryList( QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks ) ) {
00359       if ( fileName.toLower().endsWith( QLatin1String( ".png" ) ) ) {
00360         QImage image;
00361         if ( image.load( dir.path() + '/' + fileName ) ) {
00362           addImage( image, fileName );
00363         }
00364         else {
00365           kWarning() << "Unable to load image" << dir.path() + '/' + fileName;
00366         }
00367       }
00368     }
00369   }
00370 }
00371 
00372 void Signature::writeConfig( KConfigGroup &config ) const
00373 {
00374   switch ( mType ) {
00375     case Inlined:
00376       config.writeEntry( sigTypeKey, sigTypeInlineValue );
00377       config.writeEntry( sigTypeInlinedHtmlKey, mInlinedHtml );
00378       break;
00379     case FromFile:
00380       config.writeEntry( sigTypeKey, sigTypeFileValue );
00381       config.writePathEntry( sigFileKey, mUrl );
00382       break;
00383     case FromCommand:
00384       config.writeEntry( sigTypeKey, sigTypeCommandValue );
00385       config.writePathEntry( sigCommandKey, mUrl );
00386       break;
00387     case Disabled:
00388       config.writeEntry( sigTypeKey, sigTypeDisabledValue );
00389     default:
00390       break;
00391   }
00392   config.writeEntry( sigTextKey, mText );
00393   config.writeEntry( sigImageLocation, d( this )->saveLocation );
00394 
00395   cleanupImages();
00396   saveImages();
00397 }
00398 
00399 static bool isCursorAtEndOfLine( const QTextCursor &cursor )
00400 {
00401   QTextCursor testCursor = cursor;
00402   testCursor.movePosition( QTextCursor::EndOfLine, QTextCursor::KeepAnchor );
00403   return !testCursor.hasSelection();
00404 }
00405 
00406 static void insertSignatureHelper( const QString &signature,
00407                                    KRichTextEdit *textEdit,
00408                                    Signature::Placement placement,
00409                                    bool isHtml,
00410                                    bool addNewlines )
00411 {
00412   if ( !signature.isEmpty() ) {
00413 
00414     // Save the modified state of the document, as inserting a signature
00415     // shouldn't change this. Restore it at the end of this function.
00416     bool isModified = textEdit->document()->isModified();
00417 
00418     // Move to the desired position, where the signature should be inserted
00419     QTextCursor cursor = textEdit->textCursor();
00420     QTextCursor oldCursor = cursor;
00421     cursor.beginEditBlock();
00422 
00423     if ( placement == Signature::End )
00424       cursor.movePosition( QTextCursor::End );
00425     else if ( placement == Signature::Start )
00426       cursor.movePosition( QTextCursor::Start );
00427     else if ( placement == Signature::AtCursor )
00428       cursor.movePosition( QTextCursor::StartOfLine );
00429     textEdit->setTextCursor( cursor );
00430 
00431 
00432     QString lineSep;
00433     if ( addNewlines ) {
00434       if ( isHtml )
00435         lineSep = QLatin1String( "<br>" );
00436       else
00437         lineSep = QLatin1Char( '\n' );
00438     }
00439 
00440     // Insert the signature and newlines depending on where it was inserted.
00441     bool hackForCursorsAtEnd = false;
00442     int oldCursorPos = -1;
00443     if ( placement == Signature::End ) {
00444 
00445       if ( oldCursor.position() == textEdit->toPlainText().length() ) {
00446         hackForCursorsAtEnd = true;
00447         oldCursorPos = oldCursor.position();
00448       }
00449 
00450       if ( isHtml ) {
00451         textEdit->insertHtml( lineSep + signature );
00452       } else {
00453         textEdit->insertPlainText( lineSep + signature );
00454       }
00455     } else if ( placement == Signature::Start || placement == Signature::AtCursor ) {
00456       if ( isHtml ) {
00457         if ( isCursorAtEndOfLine( cursor ) )
00458           textEdit->insertHtml( signature );
00459         else
00460           textEdit->insertHtml( signature + lineSep );
00461       } else {
00462         if ( isCursorAtEndOfLine( cursor ) )
00463           textEdit->insertPlainText( signature );
00464         else
00465           textEdit->insertPlainText( signature + lineSep );
00466       }
00467     }
00468 
00469     cursor.endEditBlock();
00470 
00471     // There is one special case when re-setting the old cursor: The cursor
00472     // was at the end. In this case, QTextEdit has no way to know
00473     // if the signature was added before or after the cursor, and just decides
00474     // that it was added before (and the cursor moves to the end, but it should
00475     // not when appending a signature). See bug 167961
00476     if ( hackForCursorsAtEnd )
00477       oldCursor.setPosition( oldCursorPos );
00478 
00479     textEdit->setTextCursor( oldCursor );
00480     textEdit->ensureCursorVisible();
00481 
00482     textEdit->document()->setModified( isModified );
00483 
00484     if ( isHtml ) {
00485       textEdit->enableRichTextMode();
00486     }
00487   }
00488 }
00489 
00490 void Signature::insertIntoTextEdit( KRichTextEdit *textEdit,
00491                                     Placement placement, bool addSeparator )
00492 {
00493   QString signature;
00494   if ( addSeparator )
00495     signature = withSeparator();
00496   else
00497     signature = rawText();
00498 
00499   insertSignatureHelper( signature, textEdit, placement,
00500                    ( isInlinedHtml() &&
00501                      type() == KPIMIdentities::Signature::Inlined ),
00502                    true );
00503 }
00504 
00505 void Signature::insertIntoTextEdit( Placement placement, AddedText addedText,
00506                                     KPIMTextEdit::TextEdit *textEdit ) const
00507 {
00508   QString signature;
00509   if ( addedText & AddSeparator )
00510     signature = withSeparator();
00511   else
00512     signature = rawText();
00513 
00514   insertSignatureHelper( signature, textEdit, placement,
00515                    ( isInlinedHtml() &&
00516                      type() == KPIMIdentities::Signature::Inlined ),
00517                    ( addedText & AddNewLines ) );
00518 
00519   // We added the text of the signature above, now it is time to add the images as well.
00520   if ( isInlinedHtml() ) {
00521     foreach( const SignaturePrivate::EmbeddedImagePtr &image, d( this )->embeddedImages ) {
00522       textEdit->loadImage( image->image, image->name, image->name );
00523     }
00524   }
00525 }
00526 
00527 void Signature::insertPlainSignatureIntoTextEdit( const QString &signature, KRichTextEdit *textEdit,
00528                                                   Signature::Placement placement, bool isHtml )
00529 {
00530   insertSignatureHelper( signature, textEdit, placement, isHtml, true );
00531 }
00532 
00533 // --------------------- Operators -------------------//
00534 
00535 QDataStream &KPIMIdentities::operator<<
00536 ( QDataStream &stream, const KPIMIdentities::Signature &sig )
00537 {
00538   return stream << static_cast<quint8>( sig.mType ) << sig.mUrl << sig.mText
00539                 << d( &sig )->saveLocation << d( &sig )->embeddedImages;
00540 }
00541 
00542 QDataStream &KPIMIdentities::operator>>
00543 ( QDataStream &stream, KPIMIdentities::Signature &sig )
00544 {
00545   quint8 s;
00546   stream >> s  >> sig.mUrl >> sig.mText >> d( &sig )->saveLocation >> d( &sig )->embeddedImages;
00547   sig.mType = static_cast<Signature::Type>( s );
00548   return stream;
00549 }
00550 
00551 bool Signature::operator== ( const Signature &other ) const
00552 {
00553   if ( mType != other.mType ) {
00554     return false;
00555   }
00556 
00557   if ( mType == Inlined && mInlinedHtml ) {
00558     if ( d( this )->saveLocation != d( &other )->saveLocation )
00559       return false;
00560     if ( d( this )->embeddedImages != d( &other )->embeddedImages )
00561       return false;
00562   }
00563 
00564   switch ( mType ) {
00565   case Inlined:
00566     return mText == other.mText;
00567   case FromFile:
00568   case FromCommand:
00569     return mUrl == other.mUrl;
00570   default:
00571   case Disabled:
00572     return true;
00573   }
00574 }
00575 
00576 QString Signature::toPlainText() const
00577 {
00578   QString sigText = rawText();
00579   if ( isInlinedHtml() && type() == Inlined ) {
00580     // Use a QTextDocument as a helper, it does all the work for us and
00581     // strips all HTML tags.
00582     QTextDocument helper;
00583     QTextCursor helperCursor( &helper );
00584     helperCursor.insertHtml( sigText );
00585     sigText = helper.toPlainText();
00586   }
00587   return sigText;
00588 }
00589 
00590 void Signature::addImage ( const QImage& imageData, const QString& imageName )
00591 {
00592   Q_ASSERT( !( d( this )->saveLocation.isEmpty() ) );
00593   SignaturePrivate::EmbeddedImagePtr image( new SignaturePrivate::EmbeddedImage() );
00594   image->image = imageData;
00595   image->name = imageName;
00596   d( this )->embeddedImages.append( image );
00597 }
00598 
00599 void Signature::setImageLocation ( const QString& path )
00600 {
00601   d( this )->saveLocation = path;
00602 }
00603 
00604 // --------------- Getters -----------------------//
00605 
00606 QString Signature::text() const
00607 {
00608   return mText;
00609 }
00610 
00611 QString Signature::url() const
00612 {
00613   return mUrl;
00614 }
00615 
00616 Signature::Type Signature::type() const
00617 {
00618   return mType;
00619 }
00620 
00621 // --------------- Setters -----------------------//
00622 
00623 void Signature::setText( const QString &text )
00624 {
00625   mText = text;
00626   mType = Inlined;
00627 }
00628 
00629 void Signature::setType( Type type )
00630 {
00631   mType = type;
00632 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 22:20:45 by doxygen 1.8.0 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.8.3 API Reference

Skip menu "kdepimlibs-4.8.3 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