24 #include "emailquotehighlighter.h"
25 #include "emoticontexteditaction.h"
26 #include "inserthtmldialog.h"
27 #include "tableactionmenu.h"
28 #include "insertimagedialog.h"
30 #include <kmime/kmime_codecs.h>
32 #include <KDE/KAction>
33 #include <KDE/KActionCollection>
34 #include <KDE/KCursor>
35 #include <KDE/KFileDialog>
36 #include <KDE/KLocalizedString>
37 #include <KDE/KMessageBox>
38 #include <KDE/KPushButton>
40 #include <KDE/KImageIO>
43 #include <QtCore/QBuffer>
44 #include <QtCore/QDateTime>
45 #include <QtCore/QMimeData>
46 #include <QtCore/QFileInfo>
47 #include <QtCore/QPointer>
49 #include <QTextLayout>
51 #include "textutils.h"
52 #include <QPlainTextEdit>
54 namespace KPIMTextEdit {
61 : actionAddImage( 0 ),
62 actionDeleteLine( 0 ),
63 actionAddEmoticon( 0 ),
64 actionInsertHtml( 0 ),
66 actionFormatReset( 0 ),
68 imageSupportEnabled( false ),
69 emoticonSupportEnabled( false ),
70 insertHtmlSupportEnabled( false ),
71 insertTableSupportEnabled( false ),
72 spellCheckingEnabled( false )
84 void addImageHelper(
const QString &imageName,
const QImage &image,
85 int width = -1,
int height = -1 );
90 QList<QTextImageFormat> embeddedImageFormats()
const;
96 void fixupTextEditString( QString &text )
const;
107 void _k_slotAddImage();
109 void _k_slotDeleteLine();
111 void _k_slotAddEmoticon(
const QString & );
113 void _k_slotInsertHtml();
115 void _k_slotFormatReset();
117 void _k_slotTextModeChanged( KRichTextEdit::Mode );
120 KAction *actionAddImage;
123 KAction *actionDeleteLine;
125 EmoticonTextEditAction *actionAddEmoticon;
127 KAction *actionInsertHtml;
129 TableActionMenu *actionTable;
131 KAction *actionFormatReset;
137 bool imageSupportEnabled;
139 bool emoticonSupportEnabled;
141 bool insertHtmlSupportEnabled;
143 bool insertTableSupportEnabled;
149 QStringList mImageNames;
162 bool spellCheckingEnabled;
170 using namespace KPIMTextEdit;
172 void TextEditPrivate::fixupTextEditString( QString &text )
const
175 text.remove( QChar::LineSeparator );
179 text.remove( 0xFFFC );
182 text.replace( QChar::Nbsp, QChar::fromLatin1(
' ' ) );
186 : KRichTextWidget( text, parent ),
187 d( new TextEditPrivate( this ) )
193 : KRichTextWidget( parent ),
194 d( new TextEditPrivate( this ) )
200 : KRichTextWidget( parent ),
201 d( new TextEditPrivate( this ) )
215 KCursor::autoHideEventFilter( o, e );
218 return KRichTextWidget::eventFilter( o, e );
221 void TextEditPrivate::init()
223 q->connect( q, SIGNAL(textModeChanged(KRichTextEdit::Mode)),
224 q, SLOT(_k_slotTextModeChanged(KRichTextEdit::Mode)) );
225 q->setSpellInterface( q );
233 spellCheckingEnabled =
false;
234 q->setCheckSpellingEnabledInternal(
true );
237 KCursor::setAutoHideCursor( q,
true,
true );
239 q->installEventFilter( q );
244 return d->configFile;
249 if ( e->key() == Qt::Key_Return ) {
250 QTextCursor cursor = textCursor();
251 int oldPos = cursor.position();
252 int blockPos = cursor.block().position();
255 cursor.movePosition( QTextCursor::StartOfBlock );
256 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
257 QString lineText = cursor.selectedText();
258 if ( ( ( oldPos - blockPos ) > 0 ) &&
259 ( ( oldPos - blockPos ) <
int( lineText.length() ) ) ) {
260 bool isQuotedLine =
false;
262 while ( bot < lineText.length() ) {
263 if ( ( lineText[bot] == QChar::fromLatin1(
'>' ) ) ||
264 ( lineText[bot] == QChar::fromLatin1(
'|' ) ) ) {
267 }
else if ( lineText[bot].isSpace() ) {
273 KRichTextWidget::keyPressEvent( e );
278 ( bot != lineText.length() ) &&
279 ( ( oldPos - blockPos ) >= int( bot ) ) ) {
282 cursor.movePosition( QTextCursor::StartOfBlock );
283 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
284 QString newLine = cursor.selectedText();
288 int leadingWhiteSpaceCount = 0;
289 while ( ( leadingWhiteSpaceCount < newLine.length() ) &&
290 newLine[leadingWhiteSpaceCount].isSpace() ) {
291 ++leadingWhiteSpaceCount;
293 newLine = newLine.replace( 0, leadingWhiteSpaceCount, lineText.left( bot ) );
294 cursor.insertText( newLine );
296 cursor.movePosition( QTextCursor::StartOfBlock );
297 setTextCursor( cursor );
300 KRichTextWidget::keyPressEvent( e );
303 KRichTextWidget::keyPressEvent( e );
309 return d->spellCheckingEnabled;
319 d->spellCheckingEnabled = enable;
320 emit checkSpellingChanged( enable );
330 return quoteLength( line ) > 0;
335 bool quoteFound =
false;
336 int startOfText = -1;
337 const int lineLength( line.length() );
338 for (
int i = 0; i < lineLength; ++i ) {
339 if ( line[i] == QLatin1Char(
'>' ) || line[i] == QLatin1Char(
'|' ) ) {
341 }
else if ( line[i] != QLatin1Char(
' ' ) ) {
347 if ( startOfText == -1 ) {
348 startOfText = line.length() - 1;
358 return QLatin1String(
"> " );
368 KRichTextWidget::setHighlighter( emailHighLighter );
370 if ( !spellCheckingLanguage().isEmpty() ) {
371 setSpellCheckingLanguage( spellCheckingLanguage() );
378 Q_UNUSED( highlighter );
383 QTextDocument *doc = document();
390 QRegExp rx( QLatin1String(
"(http|ftp|ldap)s?\\S+-$" ) );
391 QTextBlock block = doc->begin();
392 while ( block.isValid() ) {
393 QTextLayout *layout = block.layout();
394 const int numberOfLine( layout->lineCount() );
395 bool urlStart =
false;
396 for (
int i = 0; i < numberOfLine; ++i ) {
397 QTextLine line = layout->lineAt( i );
398 QString lineText = block.text().mid( line.textStart(), line.textLength() );
400 if ( lineText.contains( rx ) ||
401 ( urlStart && !lineText.contains( QLatin1Char(
' ' ) ) &&
402 lineText.endsWith( QLatin1Char(
'-' ) ) ) ) {
407 temp += lineText + QLatin1Char(
'\n' );
410 block = block.next();
414 if ( temp.endsWith( QLatin1Char(
'\n' ) ) ) {
418 d->fixupTextEditString( temp );
424 QString temp = plainText;
425 d->fixupTextEditString( temp );
436 KRichTextWidget::createActions( actionCollection );
438 if ( d->imageSupportEnabled ) {
439 d->actionAddImage =
new KAction( KIcon( QLatin1String(
"insert-image" ) ),
440 i18n(
"Add Image" ),
this );
441 actionCollection->addAction( QLatin1String(
"add_image" ), d->actionAddImage );
442 connect( d->actionAddImage, SIGNAL(triggered(
bool)), SLOT(_k_slotAddImage()) );
444 if ( d->emoticonSupportEnabled ) {
445 d->actionAddEmoticon =
new EmoticonTextEditAction(
this );
446 actionCollection->addAction( QLatin1String(
"add_emoticon" ), d->actionAddEmoticon );
447 connect( d->actionAddEmoticon, SIGNAL(emoticonActivated(QString)),
448 SLOT(_k_slotAddEmoticon(QString)) );
451 if ( d->insertHtmlSupportEnabled ) {
452 d->actionInsertHtml =
new KAction( i18n(
"Insert HTML" ),
this );
453 actionCollection->addAction( QLatin1String(
"insert_html" ), d->actionInsertHtml );
454 connect( d->actionInsertHtml, SIGNAL(triggered(
bool)), SLOT(_k_slotInsertHtml()) );
457 if ( d->insertTableSupportEnabled ) {
458 d->actionTable =
new TableActionMenu( actionCollection,
this );
459 d->actionTable->setIcon( KIcon( QLatin1String(
"insert-table" ) ) );
460 d->actionTable->setText( i18n(
"Table" ) );
461 d->actionTable->setDelayed(
false );
462 actionCollection->addAction( QLatin1String(
"insert_table" ), d->actionTable );
465 d->actionDeleteLine =
new KAction( i18n(
"Delete Line" ),
this );
466 d->actionDeleteLine->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_K ) );
467 actionCollection->addAction( QLatin1String(
"delete_line" ), d->actionDeleteLine );
468 connect( d->actionDeleteLine, SIGNAL(triggered(
bool)), SLOT(_k_slotDeleteLine()) );
470 d->actionFormatReset =
471 new KAction( KIcon( QLatin1String(
"draw-eraser" ) ), i18n(
"Reset Font Settings" ),
this );
472 d->actionFormatReset->setIconText( i18n(
"Reset Font" ) );
473 actionCollection->addAction( QLatin1String(
"format_reset" ), d->actionFormatReset );
474 connect( d->actionFormatReset, SIGNAL(triggered(
bool)), SLOT(_k_slotFormatReset()) );
479 addImageHelper( url, width, height );
484 addImageHelper( url );
487 void TextEdit::addImageHelper(
const KUrl &url,
int width,
int height )
490 if ( !image.load( url.path() ) ) {
494 "Unable to load image <filename>%1</filename>.",
498 QFileInfo fi( url.path() );
500 fi.baseName().isEmpty() ?
501 QLatin1String(
"image.png" ) :
502 QString( fi.baseName() + QLatin1String(
".png" ) );
503 d->addImageHelper( imageName, image, width, height );
507 const QString &resourceName )
509 QSet<int> cursorPositionsToSkip;
510 QTextBlock currentBlock = document()->begin();
511 QTextBlock::iterator it;
512 while ( currentBlock.isValid() ) {
513 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
514 QTextFragment fragment = it.fragment();
515 if ( fragment.isValid() ) {
516 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
517 if ( imageFormat.isValid() && imageFormat.name() == matchName ) {
518 int pos = fragment.position();
519 if ( !cursorPositionsToSkip.contains( pos ) ) {
520 QTextCursor cursor( document() );
521 cursor.setPosition( pos );
522 cursor.setPosition( pos + 1, QTextCursor::KeepAnchor );
523 cursor.removeSelectedText();
524 document()->addResource( QTextDocument::ImageResource,
525 QUrl( resourceName ), QVariant( image ) );
526 QTextImageFormat format;
527 format.setName( resourceName );
528 if ( ( imageFormat.width() != 0 ) && ( imageFormat.height() != 0 ) ) {
529 format.setWidth( imageFormat.width() );
530 format.setHeight( imageFormat.height() );
532 cursor.insertImage( format );
537 cursorPositionsToSkip.insert( pos );
538 it = currentBlock.begin();
543 currentBlock = currentBlock.next();
547 void TextEditPrivate::addImageHelper(
const QString &imageName,
const QImage &image,
548 int width,
int height )
550 QString imageNameToAdd = imageName;
551 QTextDocument *document = q->document();
555 while ( mImageNames.contains( imageNameToAdd ) ) {
556 QVariant qv = document->resource( QTextDocument::ImageResource, QUrl( imageNameToAdd ) );
561 int firstDot = imageName.indexOf( QLatin1Char(
'.' ) );
562 if ( firstDot == -1 ) {
563 imageNameToAdd = imageName + QString::number( imageNumber++ );
565 imageNameToAdd = imageName.left( firstDot ) + QString::number( imageNumber++ ) +
566 imageName.mid( firstDot );
570 if ( !mImageNames.contains( imageNameToAdd ) ) {
571 document->addResource( QTextDocument::ImageResource, QUrl( imageNameToAdd ), image );
572 mImageNames << imageNameToAdd;
574 if ( width != -1 && height != -1 ) {
575 QTextImageFormat format;
576 format.setName( imageNameToAdd );
577 format.setWidth( width );
578 format.setHeight( height );
579 q->textCursor().insertImage( format );
581 q->textCursor().insertImage( imageNameToAdd );
583 q->enableRichTextMode();
588 ImageWithNameList retImages;
589 QStringList seenImageNames;
590 QList<QTextImageFormat> imageFormats = d->embeddedImageFormats();
591 foreach (
const QTextImageFormat &imageFormat, imageFormats ) {
592 if ( !seenImageNames.contains( imageFormat.name() ) ) {
593 QVariant resourceData = document()->resource( QTextDocument::ImageResource,
594 QUrl( imageFormat.name() ) );
595 QImage image = qvariant_cast<QImage>( resourceData );
596 QString name = imageFormat.name();
598 newImage->image = image;
599 newImage->name = name;
600 retImages.append( newImage );
601 seenImageNames.append( imageFormat.name() );
610 QList< QSharedPointer<EmbeddedImage> > retImages;
611 foreach (
const ImageWithNamePtr &normalImage, normalImages ) {
613 buffer.open( QIODevice::WriteOnly );
614 normalImage->image.save( &buffer,
"PNG" );
616 qsrand( QDateTime::currentDateTime().toTime_t() + qHash( normalImage->name ) );
617 QSharedPointer<EmbeddedImage> embeddedImage(
new EmbeddedImage() );
618 retImages.append( embeddedImage );
619 embeddedImage->image = KMime::Codec::codecForName(
"base64" )->encode( buffer.buffer() );
620 embeddedImage->imageName = normalImage->name;
621 embeddedImage->contentID = QString( QLatin1String(
"%1@KDE" ) ).arg( qrand() );
626 QList<QTextImageFormat> TextEditPrivate::embeddedImageFormats()
const
628 QTextDocument *doc = q->document();
629 QList<QTextImageFormat> retList;
631 QTextBlock currentBlock = doc->begin();
632 while ( currentBlock.isValid() ) {
633 QTextBlock::iterator it;
634 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
635 QTextFragment fragment = it.fragment();
636 if ( fragment.isValid() ) {
637 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
638 if ( imageFormat.isValid() ) {
640 QUrl url( imageFormat.name() );
641 if ( !url.isValid() || !url.scheme().startsWith( QLatin1String(
"http" ) ) ) {
642 retList.append( imageFormat );
647 currentBlock = currentBlock.next();
652 void TextEditPrivate::_k_slotAddEmoticon(
const QString &text )
654 QTextCursor cursor = q->textCursor();
655 cursor.insertText( text );
658 void TextEditPrivate::_k_slotInsertHtml()
660 if ( q->textMode() == KRichTextEdit::Rich ) {
661 QPointer<InsertHtmlDialog> dialog =
new InsertHtmlDialog( q );
662 if ( dialog->exec() ) {
663 const QString str = dialog->html();
664 if ( !str.isEmpty() ) {
665 QTextCursor cursor = q->textCursor();
666 cursor.insertHtml( str );
673 void TextEditPrivate::_k_slotAddImage()
675 QPointer<InsertImageDialog> dlg =
new InsertImageDialog( q );
676 if ( dlg->exec() == KDialog::Accepted && dlg ) {
677 const KUrl url = dlg->imageUrl();
679 int imageHeight = -1;
680 if ( !dlg->keepOriginalSize() ) {
681 imageWidth = dlg->imageWidth();
682 imageHeight = dlg->imageHeight();
684 q->addImage( url, imageWidth, imageHeight );
689 void TextEditPrivate::_k_slotTextModeChanged( KRichTextEdit::Mode mode )
691 if ( mode == KRichTextEdit::Rich ) {
692 saveFont = q->currentFont();
696 void TextEditPrivate::_k_slotFormatReset()
698 q->setTextBackgroundColor( q->palette().highlightedText().color() );
699 q->setTextForegroundColor( q->palette().text().color() );
700 q->setFont( saveFont );
706 d->imageSupportEnabled =
true;
711 return d->imageSupportEnabled;
716 d->emoticonSupportEnabled =
true;
721 return d->emoticonSupportEnabled;
724 void KPIMTextEdit::TextEdit::enableInsertHtmlActions()
726 d->insertHtmlSupportEnabled =
true;
731 return d->insertHtmlSupportEnabled;
736 return d->insertTableSupportEnabled;
739 void KPIMTextEdit::TextEdit::enableInsertTableActions()
741 d->insertTableSupportEnabled =
true;
745 const QByteArray &htmlBody,
const KPIMTextEdit::ImageList &imageList )
747 QByteArray result = htmlBody;
748 if ( !imageList.isEmpty() ) {
749 foreach (
const QSharedPointer<EmbeddedImage> &image, imageList ) {
750 const QString newImageName = QLatin1String(
"cid:" ) + image->contentID;
751 QByteArray quote(
"\"" );
752 result.replace( QByteArray( quote + image->imageName.toLocal8Bit() + quote ),
753 QByteArray( quote + newImageName.toLocal8Bit() + quote ) );
761 QString imageName = fileInfo.baseName().isEmpty() ?
762 i18nc(
"Start of the filename for an image",
"image" ) :
764 d->addImageHelper( imageName, image );
770 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
771 QImage image = qvariant_cast<QImage>( source->imageData() );
772 QFileInfo fi( source->text() );
779 if ( textMode() == KRichTextEdit::Plain && source->hasHtml() ) {
780 if ( source->hasText() ) {
781 insertPlainText( source->text() );
786 KRichTextWidget::insertFromMimeData( source );
791 if ( source->hasHtml() && textMode() == KRichTextEdit::Rich ) {
795 if ( source->hasText() ) {
799 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
803 return KRichTextWidget::canInsertFromMimeData( source );
808 if ( textMode() == Plain ) {
815 void TextEditPrivate::_k_slotDeleteLine()
817 if ( q->hasFocus() ) {
818 q->deleteCurrentLine();
824 QTextCursor cursor = textCursor();
825 QTextBlock block = cursor.block();
826 const QTextLayout *layout = block.layout();
830 for (
int lineNumber = 0; lineNumber < layout->lineCount(); lineNumber++ ) {
831 QTextLine line = layout->lineAt( lineNumber );
832 const bool lastLineInBlock = ( line.textStart() + line.textLength() == block.length() - 1 );
833 const bool oneLineBlock = ( layout->lineCount() == 1 );
834 const int startOfLine = block.position() + line.textStart();
835 int endOfLine = block.position() + line.textStart() + line.textLength();
836 if ( !lastLineInBlock ) {
841 if ( cursor.position() >= startOfLine && cursor.position() <= endOfLine ) {
842 int deleteStart = startOfLine;
843 int deleteLength = line.textLength();
844 if ( oneLineBlock ) {
850 if ( deleteStart + deleteLength >= document()->characterCount() &&
855 cursor.beginEditBlock();
856 cursor.setPosition( deleteStart );
857 cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor, deleteLength );
858 cursor.removeSelectedText();
859 cursor.endEditBlock();
865 #include "moc_textedit.cpp"