00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
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
00072
00073
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();
00160 }
00161
00162 QString Signature::textFromCommand( bool *ok ) const
00163 {
00164 assert( mType == FromCommand );
00165
00166
00167 if ( mUrl.isEmpty() ) {
00168 if ( ok ) {
00169 *ok = true;
00170 }
00171 return QString();
00172 }
00173
00174
00175 KProcess proc;
00176 proc.setOutputChannelMode( KProcess::SeparateChannels );
00177 proc.setShellCommand( mUrl );
00178 int rc = proc.execute();
00179
00180
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
00192 if ( ok ) {
00193 *ok = true;
00194 }
00195
00196
00197 QByteArray output = proc.readAllStandardOutput();
00198
00199
00200 return QString::fromLocal8Bit( output.data(), output.size() );
00201 }
00202
00203 QString Signature::textFromFile( bool *ok ) const
00204 {
00205 assert( mType == FromFile );
00206
00207
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
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;
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
00248 return signature;
00249 } else {
00250
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
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
00284 static QStringList findImageNames( const QString &htmlCode )
00285 {
00286 QStringList ret;
00287
00288
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
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
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
00415
00416 bool isModified = textEdit->document()->isModified();
00417
00418
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
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
00472
00473
00474
00475
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
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
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
00581
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
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
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 }