24 #include "emailquotehighlighter.h"
25 #include "emoticontexteditaction.h"
27 #include <kmime/kmime_codecs.h>
29 #include <KDE/KAction>
30 #include <KDE/KActionCollection>
31 #include <KDE/KCursor>
32 #include <KDE/KFileDialog>
33 #include <KDE/KLocalizedString>
34 #include <KDE/KMessageBox>
35 #include <KDE/KPushButton>
37 #include <KDE/KImageIO>
39 #include <QtCore/QBuffer>
40 #include <QtCore/QDateTime>
41 #include <QtCore/QMimeData>
42 #include <QtCore/QFileInfo>
43 #include <QtCore/QPointer>
44 #include <QtGui/QKeyEvent>
45 #include <QtGui/QTextLayout>
47 #include "textutils.h"
48 #include <QPlainTextEdit>
50 namespace KPIMTextEdit {
57 : actionAddImage( 0 ),
58 actionDeleteLine( 0 ),
60 imageSupportEnabled( false ),
61 emoticonSupportEnabled( false )
73 void addImageHelper(
const QString &imageName,
const QImage &image );
78 QList<QTextImageFormat> embeddedImageFormats()
const;
84 void fixupTextEditString( QString &text )
const;
95 void _k_slotAddImage();
97 void _k_slotDeleteLine();
99 void _k_slotAddEmoticon(
const QString&);
101 KAction *actionAddImage;
104 KAction *actionDeleteLine;
106 EmoticonTextEditAction *actionAddEmoticon;
111 bool imageSupportEnabled;
113 bool emoticonSupportEnabled;
119 QStringList mImageNames;
132 bool spellCheckingEnabled;
139 using namespace KPIMTextEdit;
141 void TextEditPrivate::fixupTextEditString( QString &text )
const
144 text.remove( QChar::LineSeparator );
148 text.remove( 0xFFFC );
151 text.replace( QChar::Nbsp, QChar::fromAscii(
' ' ) );
155 : KRichTextWidget( text, parent ),
156 d( new TextEditPrivate( this ) )
162 : KRichTextWidget( parent ),
163 d( new TextEditPrivate( this ) )
169 : KRichTextWidget( parent ),
170 d( new TextEditPrivate( this ) )
184 KCursor::autoHideEventFilter( o, e );
190 void TextEditPrivate::init()
192 q->setSpellInterface( q );
200 spellCheckingEnabled =
false;
201 q->setCheckSpellingEnabledInternal(
true );
204 KCursor::setAutoHideCursor( q,
true,
true );
206 q->installEventFilter( q );
211 return d->configFile;
216 if ( e->key() == Qt::Key_Return ) {
217 QTextCursor cursor = textCursor();
218 int oldPos = cursor.position();
219 int blockPos = cursor.block().position();
222 cursor.movePosition( QTextCursor::StartOfBlock );
223 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
224 QString lineText = cursor.selectedText();
225 if ( ( ( oldPos - blockPos ) > 0 ) &&
226 ( ( oldPos - blockPos ) <
int( lineText.length() ) ) ) {
227 bool isQuotedLine =
false;
229 while ( bot < lineText.length() ) {
230 if( ( lineText[bot] == QChar::fromAscii(
'>' ) ) ||
231 ( lineText[bot] == QChar::fromAscii(
'|' ) ) ) {
234 }
else if ( lineText[bot].isSpace() ) {
245 ( bot != lineText.length() ) &&
246 ( ( oldPos - blockPos ) >= int( bot ) ) ) {
249 cursor.movePosition( QTextCursor::StartOfBlock );
250 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
251 QString newLine = cursor.selectedText();
255 int leadingWhiteSpaceCount = 0;
256 while ( ( leadingWhiteSpaceCount < newLine.length() ) &&
257 newLine[leadingWhiteSpaceCount].isSpace() ) {
258 ++leadingWhiteSpaceCount;
260 newLine = newLine.replace( 0, leadingWhiteSpaceCount, lineText.left( bot ) );
261 cursor.insertText( newLine );
263 cursor.movePosition( QTextCursor::StartOfBlock );
264 setTextCursor( cursor );
276 return d->spellCheckingEnabled;
286 d->spellCheckingEnabled = enable;
287 emit checkSpellingChanged( enable );
297 return quoteLength( line ) > 0;
302 bool quoteFound =
false;
303 int startOfText = -1;
304 const int lineLength(line.length());
305 for (
int i = 0; i < lineLength; ++i ) {
306 if ( line[i] == QLatin1Char(
'>' ) || line[i] == QLatin1Char(
'|' ) ) {
308 }
else if ( line[i] != QLatin1Char(
' ' ) ) {
314 if ( startOfText == -1 ) {
315 startOfText = line.length() - 1;
325 return QLatin1String(
"> " );
335 KRichTextWidget::setHighlighter( emailHighLighter );
337 if ( !spellCheckingLanguage().isEmpty() ) {
338 setSpellCheckingLanguage( spellCheckingLanguage() );
345 Q_UNUSED( highlighter );
351 QTextDocument *doc = document();
352 QTextBlock block = doc->begin();
353 while ( block.isValid() ) {
354 QTextLayout *layout = block.layout();
355 const int numberOfLine( layout->lineCount() );
356 for (
int i = 0; i < numberOfLine; ++i ) {
357 QTextLine line = layout->lineAt( i );
358 temp += block.text().mid( line.textStart(), line.textLength() ) + QLatin1Char(
'\n' );
360 block = block.next();
364 if ( temp.endsWith( QLatin1Char(
'\n' ) ) ) {
368 d->fixupTextEditString( temp );
374 QString temp = toPlainText();
375 d->fixupTextEditString( temp );
383 if ( d->imageSupportEnabled ) {
384 d->actionAddImage =
new KAction( KIcon( QLatin1String(
"insert-image" ) ),
385 i18n(
"Add Image" ),
this );
386 actionCollection->addAction( QLatin1String(
"add_image" ), d->actionAddImage );
387 connect( d->actionAddImage, SIGNAL(triggered(
bool)), SLOT(_k_slotAddImage()) );
389 if ( d->emoticonSupportEnabled ) {
390 d->actionAddEmoticon =
new EmoticonTextEditAction(
this );
391 actionCollection->addAction( QLatin1String(
"add_emoticon" ), d->actionAddEmoticon );
392 connect( d->actionAddEmoticon, SIGNAL(emoticonActivated(QString)), SLOT(_k_slotAddEmoticon(QString)) );
395 d->actionDeleteLine =
new KAction( i18n(
"Delete Line" ),
this );
396 d->actionDeleteLine->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_K ) );
397 actionCollection->addAction( QLatin1String(
"delete_line" ), d->actionDeleteLine );
398 connect( d->actionDeleteLine, SIGNAL(triggered(
bool)), SLOT(_k_slotDeleteLine()) );
404 if ( !image.load( url.path() ) ) {
405 KMessageBox::error(
this,
407 "Unable to load image <filename>%1</filename>.",
411 QFileInfo fi( url.path() );
412 QString imageName = fi.baseName().isEmpty() ? QLatin1String(
"image.png" )
413 : QString( fi.baseName() + QLatin1String(
".png" ) );
414 d->addImageHelper( imageName, image );
418 const QString &resourceName )
420 QSet<int> cursorPositionsToSkip;
421 QTextBlock currentBlock = document()->begin();
422 QTextBlock::iterator it;
423 while ( currentBlock.isValid() ) {
424 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
425 QTextFragment fragment = it.fragment();
426 if ( fragment.isValid() ) {
427 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
428 if ( imageFormat.isValid() && imageFormat.name() == matchName ) {
429 int pos = fragment.position();
430 if ( !cursorPositionsToSkip.contains( pos ) ) {
431 QTextCursor cursor( document() );
432 cursor.setPosition( pos );
433 cursor.setPosition( pos + 1, QTextCursor::KeepAnchor );
434 cursor.removeSelectedText();
435 document()->addResource( QTextDocument::ImageResource,
436 QUrl( resourceName ), QVariant( image ) );
437 QTextImageFormat format;
438 format.setName( resourceName );
439 if ( (imageFormat.width()!=0) && (imageFormat.height()!=0) ) {
440 format.setWidth( imageFormat.width() );
441 format.setHeight( imageFormat.height() );
443 cursor.insertImage( format );
449 cursorPositionsToSkip.insert( pos );
450 it = currentBlock.begin();
455 currentBlock = currentBlock.next();
459 void TextEditPrivate::addImageHelper(
const QString &imageName,
const QImage &image )
461 QString imageNameToAdd = imageName;
462 QTextDocument *document = q->document();
466 while ( mImageNames.contains( imageNameToAdd ) ) {
467 QVariant qv = document->resource( QTextDocument::ImageResource, QUrl( imageNameToAdd ) );
472 int firstDot = imageName.indexOf( QLatin1Char(
'.' ) );
473 if ( firstDot == -1 ) {
474 imageNameToAdd = imageName + QString::number( imageNumber++ );
476 imageNameToAdd = imageName.left( firstDot ) + QString::number( imageNumber++ ) +
477 imageName.mid( firstDot );
481 if ( !mImageNames.contains( imageNameToAdd ) ) {
482 document->addResource( QTextDocument::ImageResource, QUrl( imageNameToAdd ), image );
483 mImageNames << imageNameToAdd;
485 q->textCursor().insertImage( imageNameToAdd );
486 q->enableRichTextMode();
491 ImageWithNameList retImages;
492 QStringList seenImageNames;
493 QList<QTextImageFormat> imageFormats = d->embeddedImageFormats();
494 foreach (
const QTextImageFormat &imageFormat, imageFormats ) {
495 if ( !seenImageNames.contains( imageFormat.name() ) ) {
496 QVariant resourceData = document()->resource( QTextDocument::ImageResource,
497 QUrl( imageFormat.name() ) );
498 QImage image = qvariant_cast<QImage>( resourceData );
499 QString name = imageFormat.name();
501 newImage->image = image;
502 newImage->name = name;
503 retImages.append( newImage );
504 seenImageNames.append( imageFormat.name() );
513 QList< QSharedPointer<EmbeddedImage> > retImages;
514 foreach (
const ImageWithNamePtr &normalImage, normalImages ) {
516 buffer.open( QIODevice::WriteOnly );
517 normalImage->image.save( &buffer,
"PNG" );
519 qsrand( QDateTime::currentDateTime().toTime_t() + qHash( normalImage->name ) );
520 QSharedPointer<EmbeddedImage> embeddedImage(
new EmbeddedImage() );
521 retImages.append( embeddedImage );
522 embeddedImage->image = KMime::Codec::codecForName(
"base64" )->encode( buffer.buffer() );
523 embeddedImage->imageName = normalImage->name;
524 embeddedImage->contentID = QString( QLatin1String(
"%1@KDE" ) ).arg( qrand() );
529 QList<QTextImageFormat> TextEditPrivate::embeddedImageFormats()
const
531 QTextDocument *doc = q->document();
532 QList<QTextImageFormat> retList;
534 QTextBlock currentBlock = doc->begin();
535 while ( currentBlock.isValid() ) {
536 QTextBlock::iterator it;
537 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
538 QTextFragment fragment = it.fragment();
539 if ( fragment.isValid() ) {
540 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
541 if ( imageFormat.isValid() ) {
543 QUrl url(imageFormat.name());
544 if( !url.isValid() || !url.scheme().startsWith( QLatin1String(
"http" ) ) ) {
545 retList.append( imageFormat );
550 currentBlock = currentBlock.next();
555 void TextEditPrivate::_k_slotAddEmoticon(
const QString& text)
557 QTextCursor cursor = q->textCursor();
558 cursor.insertText( text );
561 void TextEditPrivate::_k_slotAddImage()
563 QStringList mimetypes = KImageIO::mimeTypes( KImageIO::Reading );
564 QPointer<KFileDialog> fdlg =
new KFileDialog( QString(), mimetypes.join(QLatin1String(
" ")), q );
565 fdlg->setOperationMode( KFileDialog::Other );
566 fdlg->setCaption( i18n(
"Add Image" ) );
567 fdlg->okButton()->setGuiItem( KGuiItem( i18n(
"&Add" ), QLatin1String(
"document-open" ) ) );
568 fdlg->setMode( KFile::Files );
569 if ( fdlg->exec() == KDialog::Accepted && fdlg ) {
570 const KUrl::List files = fdlg->selectedUrls();
571 foreach (
const KUrl &url, files ) {
580 d->imageSupportEnabled =
true;
585 return d->imageSupportEnabled;
590 d->emoticonSupportEnabled =
true;
595 return d->emoticonSupportEnabled;
600 const QByteArray &htmlBody,
const KPIMTextEdit::ImageList &imageList )
602 QByteArray result = htmlBody;
603 if ( !imageList.isEmpty() ) {
604 foreach (
const QSharedPointer<EmbeddedImage> &image, imageList ) {
605 const QString newImageName = QLatin1String(
"cid:" ) + image->contentID;
606 QByteArray quote(
"\"" );
607 result.replace( QByteArray( quote + image->imageName.toLocal8Bit() + quote ),
608 QByteArray( quote + newImageName.toLocal8Bit() + quote ) );
616 QString imageName = fileInfo.baseName().isEmpty() ?
617 i18nc(
"Start of the filename for an image",
"image" ) :
619 d->addImageHelper( imageName, image );
625 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
626 QImage image = qvariant_cast<QImage>( source->imageData() );
627 QFileInfo fi( source->text() );
634 if ( textMode() == KRichTextEdit::Plain && source->hasHtml() ) {
635 if ( source->hasText() ) {
636 insertPlainText( source->text() );
646 if ( source->hasHtml() && textMode() == KRichTextEdit::Rich ) {
650 if ( source->hasText() ) {
654 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
658 return KRichTextWidget::canInsertFromMimeData( source );
663 if ( textMode() == Plain ) {
670 void TextEditPrivate::_k_slotDeleteLine()
672 if ( q->hasFocus() ) {
673 q->deleteCurrentLine();
679 QTextCursor cursor = textCursor();
680 QTextBlock block = cursor.block();
681 const QTextLayout *layout = block.layout();
685 for (
int lineNumber = 0; lineNumber < layout->lineCount(); lineNumber++ ) {
686 QTextLine line = layout->lineAt( lineNumber );
687 const bool lastLineInBlock = ( line.textStart() + line.textLength() == block.length() - 1 );
688 const bool oneLineBlock = ( layout->lineCount() == 1 );
689 const int startOfLine = block.position() + line.textStart();
690 int endOfLine = block.position() + line.textStart() + line.textLength();
691 if ( !lastLineInBlock ) {
696 if ( cursor.position() >= startOfLine && cursor.position() <= endOfLine ) {
697 int deleteStart = startOfLine;
698 int deleteLength = line.textLength();
699 if ( oneLineBlock ) {
705 if ( deleteStart + deleteLength >= document()->characterCount() &&
710 cursor.beginEditBlock();
711 cursor.setPosition( deleteStart );
712 cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor, deleteLength );
713 cursor.removeSelectedText();
714 cursor.endEditBlock();
721 #include "textedit.moc"