00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "textedit.h"
00023
00024 #include "emailquotehighlighter.h"
00025
00026 #include <kmime/kmime_codecs.h>
00027
00028 #include <KDE/KAction>
00029 #include <KDE/KActionCollection>
00030 #include <KDE/KCursor>
00031 #include <KDE/KFileDialog>
00032 #include <KDE/KLocalizedString>
00033 #include <KDE/KMessageBox>
00034 #include <KDE/KPushButton>
00035 #include <KDE/KUrl>
00036
00037 #include <QtCore/QBuffer>
00038 #include <QtCore/QDateTime>
00039 #include <QtCore/QMimeData>
00040 #include <QtCore/QFileInfo>
00041 #include <QtCore/QPointer>
00042 #include <QtGui/QKeyEvent>
00043 #include <QtGui/QTextLayout>
00044
00045 namespace KPIMTextEdit {
00046
00047 class TextEditPrivate
00048 {
00049 public:
00050
00051 TextEditPrivate( TextEdit *parent )
00052 : q( parent ),
00053 imageSupportEnabled( false )
00054 {
00055 }
00056
00065 void addImageHelper( const QString &imageName, const QImage &image );
00066
00070 QList<QTextImageFormat> embeddedImageFormats() const;
00071
00076 void fixupTextEditString( QString &text ) const;
00077
00081 void init();
00082
00087 void _k_slotAddImage();
00088
00090 KAction *actionAddImage;
00091
00093 TextEdit *q;
00094
00096 bool imageSupportEnabled;
00097
00103 QStringList mImageNames;
00104
00116 bool spellCheckingEnabled;
00117 };
00118
00119 }
00120
00121 using namespace KPIMTextEdit;
00122
00123 void TextEditPrivate::fixupTextEditString( QString &text ) const
00124 {
00125
00126 text.remove( QChar::LineSeparator );
00127
00128
00129
00130 text.remove( 0xFFFC );
00131
00132
00133 text.replace( QChar::Nbsp, QChar::fromAscii( ' ' ) );
00134 }
00135
00136 TextEdit::TextEdit( const QString& text, QWidget *parent )
00137 : KRichTextWidget( text, parent ),
00138 d( new TextEditPrivate( this ) )
00139 {
00140 d->init();
00141 }
00142
00143 TextEdit::TextEdit( QWidget *parent )
00144 : KRichTextWidget( parent ),
00145 d( new TextEditPrivate( this ) )
00146 {
00147 d->init();
00148 }
00149
00150 TextEdit::~TextEdit()
00151 {
00152 }
00153
00154 bool TextEdit::eventFilter( QObject*o, QEvent* e )
00155 {
00156 if ( o == this )
00157 KCursor::autoHideEventFilter( o, e );
00158 return KRichTextWidget::eventFilter( o, e );
00159 }
00160
00161 void TextEditPrivate::init()
00162 {
00163 q->setSpellInterface( q );
00164
00165
00166
00167
00168
00169
00170
00171 spellCheckingEnabled = false;
00172 q->setCheckSpellingEnabledInternal( true );
00173
00174 KCursor::setAutoHideCursor( q, true, true );
00175 q->installEventFilter( q );
00176 }
00177
00178 void TextEdit::keyPressEvent ( QKeyEvent * e )
00179 {
00180 if ( e->key() == Qt::Key_Return ) {
00181 QTextCursor cursor = textCursor();
00182 int oldPos = cursor.position();
00183 int blockPos = cursor.block().position();
00184
00185
00186 cursor.movePosition( QTextCursor::StartOfBlock );
00187 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
00188 QString lineText = cursor.selectedText();
00189 if ( ( ( oldPos -blockPos ) > 0 ) &&
00190 ( ( oldPos-blockPos ) < int( lineText.length() ) ) ) {
00191 bool isQuotedLine = false;
00192 int bot = 0;
00193 while ( bot < lineText.length() ) {
00194 if( ( lineText[bot] == QChar::fromAscii( '>' ) ) ||
00195 ( lineText[bot] == QChar::fromAscii( '|' ) ) ) {
00196 isQuotedLine = true;
00197 ++bot;
00198 }
00199 else if ( lineText[bot].isSpace() ) {
00200 ++bot;
00201 }
00202 else {
00203 break;
00204 }
00205 }
00206 KRichTextWidget::keyPressEvent( e );
00207
00208
00209
00210 if ( isQuotedLine
00211 && ( bot != lineText.length() )
00212 && ( ( oldPos-blockPos ) >= int( bot ) ) ) {
00213
00214
00215 cursor.movePosition( QTextCursor::StartOfBlock );
00216 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
00217 QString newLine = cursor.selectedText();
00218
00219
00220
00221 int leadingWhiteSpaceCount = 0;
00222 while ( ( leadingWhiteSpaceCount < newLine.length() )
00223 && newLine[leadingWhiteSpaceCount].isSpace() ) {
00224 ++leadingWhiteSpaceCount;
00225 }
00226 newLine = newLine.replace( 0, leadingWhiteSpaceCount,
00227 lineText.left( bot ) );
00228 cursor.insertText( newLine );
00229
00230 cursor.movePosition( QTextCursor::StartOfBlock );
00231 setTextCursor( cursor );
00232 }
00233 }
00234 else
00235 KRichTextWidget::keyPressEvent( e );
00236 }
00237 else
00238 {
00239 KRichTextWidget::keyPressEvent( e );
00240 }
00241 }
00242
00243
00244 bool TextEdit::isSpellCheckingEnabled() const
00245 {
00246 return d->spellCheckingEnabled;
00247 }
00248
00249 void TextEdit::setSpellCheckingEnabled( bool enable )
00250 {
00251 EMailQuoteHighlighter *hlighter =
00252 dynamic_cast<EMailQuoteHighlighter*>( highlighter() );
00253 if ( hlighter )
00254 hlighter->toggleSpellHighlighting( enable );
00255
00256 d->spellCheckingEnabled = enable;
00257 emit checkSpellingChanged( enable );
00258 }
00259
00260 bool TextEdit::shouldBlockBeSpellChecked( const QString& block ) const
00261 {
00262 return !isLineQuoted( block );
00263 }
00264
00265 bool KPIMTextEdit::TextEdit::isLineQuoted( const QString& line ) const
00266 {
00267 return quoteLength( line ) > 0;
00268 }
00269
00270 int KPIMTextEdit::TextEdit::quoteLength( const QString& line ) const
00271 {
00272 bool quoteFound = false;
00273 int startOfText = -1;
00274 for ( int i = 0; i < line.length(); i++ ) {
00275 if ( line[i] == QLatin1Char( '>' ) || line[i] == QLatin1Char( '|' ) )
00276 quoteFound = true;
00277 else if ( line[i] != QLatin1Char( ' ' ) ) {
00278 startOfText = i;
00279 break;
00280 }
00281 }
00282 if ( quoteFound ) {
00283 if ( startOfText == -1 )
00284 startOfText = line.length() - 1;
00285 return startOfText;
00286 }
00287 else
00288 return 0;
00289 }
00290
00291 const QString KPIMTextEdit::TextEdit::defaultQuoteSign() const
00292 {
00293 return QLatin1String( "> " );
00294 }
00295
00296 void TextEdit::createHighlighter()
00297 {
00298 EMailQuoteHighlighter *emailHighLighter =
00299 new EMailQuoteHighlighter( this );
00300
00301 setHighlighterColors( emailHighLighter );
00302
00303
00304 KRichTextWidget::setHighlighter( emailHighLighter );
00305
00306 if ( !spellCheckingLanguage().isEmpty() )
00307 setSpellCheckingLanguage( spellCheckingLanguage() );
00308 setSpellCheckingEnabled( isSpellCheckingEnabled() );
00309 }
00310
00311 void TextEdit::setHighlighterColors( EMailQuoteHighlighter *highlighter )
00312 {
00313 Q_UNUSED( highlighter );
00314 }
00315
00316 QString TextEdit::toWrappedPlainText() const
00317 {
00318 QString temp;
00319 QTextDocument* doc = document();
00320 QTextBlock block = doc->begin();
00321 while ( block.isValid() ) {
00322 QTextLayout* layout = block.layout();
00323 for ( int i = 0; i < layout->lineCount(); i++ ) {
00324 QTextLine line = layout->lineAt( i );
00325 temp += block.text().mid( line.textStart(), line.textLength() ) + QLatin1Char( '\n' );
00326 }
00327 block = block.next();
00328 }
00329
00330
00331 if ( temp.endsWith( QLatin1Char( '\n' ) ) )
00332 temp.chop( 1 );
00333
00334 d->fixupTextEditString( temp );
00335 return temp;
00336 }
00337
00338 QString TextEdit::toCleanPlainText() const
00339 {
00340 QString temp = toPlainText();
00341 d->fixupTextEditString( temp );
00342 return temp;
00343 }
00344
00345 void TextEdit::createActions( KActionCollection *actionCollection )
00346 {
00347 KRichTextWidget::createActions( actionCollection );
00348
00349 if ( d->imageSupportEnabled ) {
00350 d->actionAddImage = new KAction( KIcon( QLatin1String( "insert-image" ) ),
00351 i18n( "Add Image" ), this );
00352 actionCollection->addAction( QLatin1String( "add_image" ), d->actionAddImage );
00353 connect( d->actionAddImage, SIGNAL(triggered(bool) ), SLOT( _k_slotAddImage() ) );
00354 }
00355 }
00356
00357 void TextEdit::addImage( const KUrl &url )
00358 {
00359 QImage image;
00360 if ( !image.load( url.path() ) ) {
00361 KMessageBox::error( this,
00362 i18nc( "@info", "Unable to load image <filename>%1</filename>.", url.path() ) );
00363 return;
00364 }
00365 QFileInfo fi( url.path() );
00366 QString imageName = fi.baseName().isEmpty() ? QLatin1String( "image.png" )
00367 : fi.baseName() + QLatin1String( ".png" );
00368 d->addImageHelper( imageName, image );
00369 }
00370
00371 void TextEditPrivate::addImageHelper( const QString &imageName, const QImage &image )
00372 {
00373 QString imageNameToAdd = imageName;
00374 QTextDocument *document = q->document();
00375
00376
00377 int imageNumber = 1;
00378 while ( mImageNames.contains( imageNameToAdd ) ) {
00379 QVariant qv = document->resource( QTextDocument::ImageResource, QUrl( imageNameToAdd ) );
00380 if ( qv == image ) {
00381
00382 break;
00383 }
00384 int firstDot = imageName.indexOf( QLatin1Char( '.' ) );
00385 if ( firstDot == -1 )
00386 imageNameToAdd = imageName + QString::number( imageNumber++ );
00387 else
00388 imageNameToAdd = imageName.left( firstDot ) + QString::number( imageNumber++ ) +
00389 imageName.mid( firstDot );
00390 }
00391
00392 if ( !mImageNames.contains( imageNameToAdd ) ) {
00393 document->addResource( QTextDocument::ImageResource, QUrl( imageNameToAdd ), image );
00394 mImageNames << imageNameToAdd;
00395 }
00396 q->textCursor().insertImage( imageNameToAdd );
00397 q->enableRichTextMode();
00398 }
00399
00400 QList< QSharedPointer<EmbeddedImage> > TextEdit::embeddedImages() const
00401 {
00402 QList< QSharedPointer<EmbeddedImage> > retImages;
00403 QStringList seenImageNames;
00404 QList<QTextImageFormat> imageFormats = d->embeddedImageFormats();
00405 foreach( const QTextImageFormat &imageFormat, imageFormats ) {
00406 if ( !seenImageNames.contains( imageFormat.name() ) ) {
00407 QVariant data = document()->resource( QTextDocument::ImageResource, QUrl( imageFormat.name() ) );
00408 QImage image = qvariant_cast<QImage>( data );
00409 QBuffer buffer;
00410 buffer.open( QIODevice::WriteOnly );
00411 image.save( &buffer, "PNG" );
00412
00413 qsrand( QDateTime::currentDateTime().toTime_t() + qHash( imageFormat.name() ) );
00414 QSharedPointer<EmbeddedImage> embeddedImage( new EmbeddedImage() );
00415 retImages.append( embeddedImage );
00416 embeddedImage->image = KMime::Codec::codecForName( "base64" )->encode( buffer.buffer() );
00417 embeddedImage->imageName = imageFormat.name();
00418 embeddedImage->contentID = QString( QLatin1String( "%1" ) ).arg( qrand() );
00419 seenImageNames.append( imageFormat.name() );
00420 }
00421 }
00422 return retImages;
00423 }
00424
00425 QList<QTextImageFormat> TextEditPrivate::embeddedImageFormats() const
00426 {
00427 QTextDocument *doc = q->document();
00428 QList<QTextImageFormat> retList;
00429
00430 QTextBlock currentBlock = doc->begin();
00431 while ( currentBlock.isValid() ) {
00432 QTextBlock::iterator it;
00433 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
00434 QTextFragment fragment = it.fragment();
00435 if ( fragment.isValid() ) {
00436 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
00437 if ( imageFormat.isValid() ) {
00438 retList.append( imageFormat );
00439 }
00440 }
00441 }
00442 currentBlock = currentBlock.next();
00443 }
00444 return retList;
00445 }
00446
00447 void TextEditPrivate::_k_slotAddImage()
00448 {
00449 QPointer<KFileDialog> fdlg = new KFileDialog( QString(), QString(), q );
00450 fdlg->setOperationMode( KFileDialog::Other );
00451 fdlg->setCaption( i18n("Add Image") );
00452 fdlg->okButton()->setGuiItem( KGuiItem( i18n("&Add"), QLatin1String( "document-open" ) ) );
00453 fdlg->setMode( KFile::Files );
00454 if ( fdlg->exec() != KDialog::Accepted ) {
00455 delete fdlg;
00456 return;
00457 }
00458
00459 const KUrl::List files = fdlg->selectedUrls();
00460 foreach ( const KUrl& url, files ) {
00461 q->addImage( url );
00462 }
00463 delete fdlg;
00464 }
00465
00466 void KPIMTextEdit::TextEdit::enableImageActions()
00467 {
00468 d->imageSupportEnabled = true;
00469 }
00470
00471 QByteArray KPIMTextEdit::TextEdit::imageNamesToContentIds( const QByteArray &htmlBody, const KPIMTextEdit::ImageList &imageList )
00472 {
00473 QByteArray result = htmlBody;
00474 if ( imageList.size() > 0 ) {
00475 foreach( const QSharedPointer<EmbeddedImage> &image, imageList ) {
00476 const QString newImageName = QLatin1String( "cid:" ) + image->contentID;
00477 QByteArray quote( "\"" );
00478 result.replace( QByteArray( quote + image->imageName.toLocal8Bit() + quote ),
00479 QByteArray( quote + newImageName.toLocal8Bit() + quote ) );
00480 }
00481 }
00482 return result;
00483 }
00484
00485 void TextEdit::insertFromMimeData( const QMimeData *source )
00486 {
00487
00488 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
00489 QImage image = qvariant_cast<QImage>( source->imageData() );
00490 QFileInfo fi( source->text() );
00491 QString imageName = fi.baseName().isEmpty() ? i18nc( "Start of the filename for an image", "image" ) : fi.baseName();
00492 d->addImageHelper( imageName, image );
00493 return;
00494 }
00495
00496
00497
00498 if ( textMode() == KRichTextEdit::Plain && source->hasHtml() ) {
00499 if ( source->hasText() ) {
00500 insertPlainText( source->text() );
00501 return;
00502 }
00503 }
00504
00505 KRichTextWidget::insertFromMimeData( source );
00506 }
00507
00508 bool KPIMTextEdit::TextEdit::canInsertFromMimeData( const QMimeData *source ) const
00509 {
00510 if ( source->hasHtml() && textMode() == KRichTextEdit::Rich )
00511 return true;
00512 if ( source->hasText() )
00513 return true;
00514 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled )
00515 return true;
00516
00517 return KRichTextWidget::canInsertFromMimeData( source );
00518 }
00519
00520 static bool isCharFormatFormatted( const QTextCharFormat &format, const QFont &defaultFont,
00521 const QTextCharFormat &defaultBlockFormat )
00522 {
00523 if ( !format.anchorHref().isEmpty() ||
00524 format.font() != defaultFont ||
00525 format.isAnchor() ||
00526 format.verticalAlignment() != defaultBlockFormat.verticalAlignment() ||
00527 format.underlineStyle() != defaultBlockFormat.underlineStyle() ||
00528 format.foreground().color() != defaultBlockFormat.foreground().color() ||
00529 format.background().color() != defaultBlockFormat.background().color() )
00530 return true;
00531
00532 return false;
00533 }
00534
00535 static bool isBlockFormatFormatted( const QTextBlockFormat &format,
00536 const QTextBlockFormat &defaultFormat )
00537 {
00538 if ( format.alignment() != defaultFormat.alignment() ||
00539 format.indent() != defaultFormat.indent() ||
00540 format.textIndent() != defaultFormat.textIndent() )
00541 return true;
00542
00543 return false;
00544 }
00545
00547 static bool isSpecial( const QTextFormat &charFormat )
00548 {
00549 return charFormat.isFrameFormat() || charFormat.isImageFormat() ||
00550 charFormat.isListFormat() || charFormat.isTableFormat();
00551 }
00552
00553 bool TextEdit::isFormattingUsed() const
00554 {
00555 if ( textMode() == Plain )
00556 return false;
00557
00558
00559
00560
00561
00562
00563
00564
00565
00566
00567
00568 QTextEdit defaultTextEdit;
00569 QTextCharFormat defaultCharFormat = defaultTextEdit.document()->begin().charFormat();
00570 QTextBlockFormat defaultBlockFormat = defaultTextEdit.document()->begin().blockFormat();
00571 QFont defaultFont = document()->defaultFont();
00572
00573 QTextBlock block = document()->firstBlock();
00574 while ( block.isValid() ) {
00575
00576 if ( isBlockFormatFormatted( block.blockFormat(), defaultBlockFormat ) ) {
00577 return true;
00578 }
00579
00580 if ( isSpecial( block.charFormat() ) || isSpecial( block.blockFormat() ) ||
00581 block.textList() ) {
00582 return true;
00583 }
00584
00585 QTextBlock::iterator it = block.begin();
00586 while ( !it.atEnd() ) {
00587 QTextFragment fragment = it.fragment();
00588 QTextCharFormat charFormat = fragment.charFormat();
00589 if ( isSpecial( charFormat ) ) {
00590 return true;
00591 }
00592 if ( isCharFormatFormatted( fragment.charFormat(), defaultFont, defaultCharFormat ) ) {
00593 return true;
00594 }
00595
00596 it++;
00597 }
00598 block = block.next();
00599 }
00600
00601 if ( toHtml().contains( QLatin1String( "<hr />" ) ) )
00602 return true;
00603
00604 return false;
00605 }
00606
00607 #include "textedit.moc"