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>
42 #include <QtCore/QBuffer>
43 #include <QtCore/QDateTime>
44 #include <QtCore/QMimeData>
45 #include <QtCore/QFileInfo>
46 #include <QtCore/QPointer>
48 #include <QTextLayout>
50 #include "textutils.h"
51 #include <QPlainTextEdit>
53 namespace KPIMTextEdit {
60 : actionAddImage( 0 ),
61 actionDeleteLine( 0 ),
62 actionAddEmoticon( 0 ),
63 actionInsertHtml( 0 ),
65 actionFormatReset( 0 ),
67 imageSupportEnabled( false ),
68 emoticonSupportEnabled( false ),
69 insertHtmlSupportEnabled( false ),
70 insertTableSupportEnabled( false ),
71 spellCheckingEnabled( false )
83 void addImageHelper(
const QString &imageName,
const QImage &image,
int width = -1,
int height = -1);
88 QList<QTextImageFormat> embeddedImageFormats()
const;
94 void fixupTextEditString( QString &text )
const;
105 void _k_slotAddImage();
107 void _k_slotDeleteLine();
109 void _k_slotAddEmoticon(
const QString&);
111 void _k_slotInsertHtml();
113 void _k_slotFormatReset();
115 void _k_slotTextModeChanged(KRichTextEdit::Mode);
118 KAction *actionAddImage;
121 KAction *actionDeleteLine;
123 EmoticonTextEditAction *actionAddEmoticon;
125 KAction *actionInsertHtml;
127 TableActionMenu *actionTable;
129 KAction *actionFormatReset;
135 bool imageSupportEnabled;
137 bool emoticonSupportEnabled;
139 bool insertHtmlSupportEnabled;
141 bool insertTableSupportEnabled;
147 QStringList mImageNames;
160 bool spellCheckingEnabled;
168 using namespace KPIMTextEdit;
170 void TextEditPrivate::fixupTextEditString( QString &text )
const
173 text.remove( QChar::LineSeparator );
177 text.remove( 0xFFFC );
180 text.replace( QChar::Nbsp, QChar::fromLatin1(
' ' ) );
184 : KRichTextWidget( text, parent ),
185 d( new TextEditPrivate( this ) )
191 : KRichTextWidget( parent ),
192 d( new TextEditPrivate( this ) )
198 : KRichTextWidget( parent ),
199 d( new TextEditPrivate( this ) )
213 KCursor::autoHideEventFilter( o, e );
216 return KRichTextWidget::eventFilter( o, e );
219 void TextEditPrivate::init()
221 q->connect(q,SIGNAL(textModeChanged(KRichTextEdit::Mode)),q,SLOT(_k_slotTextModeChanged(KRichTextEdit::Mode)));
222 q->setSpellInterface( q );
230 spellCheckingEnabled =
false;
231 q->setCheckSpellingEnabledInternal(
true );
234 KCursor::setAutoHideCursor( q,
true,
true );
236 q->installEventFilter( q );
241 return d->configFile;
246 if ( e->key() == Qt::Key_Return ) {
247 QTextCursor cursor = textCursor();
248 int oldPos = cursor.position();
249 int blockPos = cursor.block().position();
252 cursor.movePosition( QTextCursor::StartOfBlock );
253 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
254 QString lineText = cursor.selectedText();
255 if ( ( ( oldPos - blockPos ) > 0 ) &&
256 ( ( oldPos - blockPos ) <
int( lineText.length() ) ) ) {
257 bool isQuotedLine =
false;
259 while ( bot < lineText.length() ) {
260 if ( ( lineText[bot] == QChar::fromLatin1(
'>' ) ) ||
261 ( lineText[bot] == QChar::fromLatin1(
'|' ) ) ) {
264 }
else if ( lineText[bot].isSpace() ) {
270 KRichTextWidget::keyPressEvent( e );
275 ( bot != lineText.length() ) &&
276 ( ( oldPos - blockPos ) >= int( bot ) ) ) {
279 cursor.movePosition( QTextCursor::StartOfBlock );
280 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
281 QString newLine = cursor.selectedText();
285 int leadingWhiteSpaceCount = 0;
286 while ( ( leadingWhiteSpaceCount < newLine.length() ) &&
287 newLine[leadingWhiteSpaceCount].isSpace() ) {
288 ++leadingWhiteSpaceCount;
290 newLine = newLine.replace( 0, leadingWhiteSpaceCount, lineText.left( bot ) );
291 cursor.insertText( newLine );
293 cursor.movePosition( QTextCursor::StartOfBlock );
294 setTextCursor( cursor );
297 KRichTextWidget::keyPressEvent( e );
300 KRichTextWidget::keyPressEvent( e );
306 return d->spellCheckingEnabled;
316 d->spellCheckingEnabled = enable;
317 emit checkSpellingChanged( enable );
327 return quoteLength( line ) > 0;
332 bool quoteFound =
false;
333 int startOfText = -1;
334 const int lineLength(line.length());
335 for (
int i = 0; i < lineLength; ++i ) {
336 if ( line[i] == QLatin1Char(
'>' ) || line[i] == QLatin1Char(
'|' ) ) {
338 }
else if ( line[i] != QLatin1Char(
' ' ) ) {
344 if ( startOfText == -1 ) {
345 startOfText = line.length() - 1;
355 return QLatin1String(
"> " );
365 KRichTextWidget::setHighlighter( emailHighLighter );
367 if ( !spellCheckingLanguage().isEmpty() ) {
368 setSpellCheckingLanguage( spellCheckingLanguage() );
375 Q_UNUSED( highlighter );
381 QTextDocument *doc = document();
388 QRegExp rx( QLatin1String(
"(http|ftp|ldap)s?\\S+-$" ) );
389 QTextBlock block = doc->begin();
390 while ( block.isValid() ) {
391 QTextLayout *layout = block.layout();
392 const int numberOfLine( layout->lineCount() );
393 bool urlStart =
false;
394 for (
int i = 0; i < numberOfLine; ++i ) {
395 QTextLine line = layout->lineAt( i );
396 QString lineText = block.text().mid( line.textStart(), line.textLength() );
398 if ( lineText.contains(rx) || ( urlStart && !lineText.contains( QLatin1Char(
' ') ) && lineText.endsWith( QLatin1Char(
'-') ) ) ) {
403 temp += lineText + QLatin1Char(
'\n' );
406 block = block.next();
410 if ( temp.endsWith( QLatin1Char(
'\n' ) ) ) {
414 d->fixupTextEditString( temp );
420 QString temp = plainText;
421 d->fixupTextEditString( temp );
432 KRichTextWidget::createActions( actionCollection );
434 if ( d->imageSupportEnabled ) {
435 d->actionAddImage =
new KAction( KIcon( QLatin1String(
"insert-image" ) ),
436 i18n(
"Add Image" ),
this );
437 actionCollection->addAction( QLatin1String(
"add_image" ), d->actionAddImage );
438 connect( d->actionAddImage, SIGNAL(triggered(
bool)), SLOT(_k_slotAddImage()) );
440 if ( d->emoticonSupportEnabled ) {
441 d->actionAddEmoticon =
new EmoticonTextEditAction(
this );
442 actionCollection->addAction( QLatin1String(
"add_emoticon" ), d->actionAddEmoticon );
443 connect( d->actionAddEmoticon, SIGNAL(emoticonActivated(QString)), SLOT(_k_slotAddEmoticon(QString)) );
446 if ( d->insertHtmlSupportEnabled ) {
447 d->actionInsertHtml =
new KAction( i18n(
"Insert HTML" ),
this );
448 actionCollection->addAction( QLatin1String(
"insert_html" ), d->actionInsertHtml );
449 connect( d->actionInsertHtml, SIGNAL(triggered(
bool)), SLOT(_k_slotInsertHtml()) );
452 if ( d->insertTableSupportEnabled ) {
453 d->actionTable =
new TableActionMenu( actionCollection,
this );
454 d->actionTable->setIcon( KIcon( QLatin1String(
"table" ) ) );
455 d->actionTable->setText( i18n(
"Table" ) );
456 d->actionTable->setDelayed(
false );
457 actionCollection->addAction( QLatin1String(
"insert_table" ), d->actionTable );
461 d->actionDeleteLine =
new KAction( i18n(
"Delete Line" ),
this );
462 d->actionDeleteLine->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_K ) );
463 actionCollection->addAction( QLatin1String(
"delete_line" ), d->actionDeleteLine );
464 connect( d->actionDeleteLine, SIGNAL(triggered(
bool)), SLOT(_k_slotDeleteLine()) );
466 d->actionFormatReset =
new KAction( KIcon( QLatin1String(
"draw-eraser") ), i18n(
"Reset Font Settings"),
this );
467 d->actionFormatReset->setIconText( i18n(
"Reset Font") );
468 actionCollection->addAction( QLatin1String(
"format_reset"), d->actionFormatReset );
469 connect( d->actionFormatReset, SIGNAL(triggered(
bool)), SLOT(_k_slotFormatReset()) );
476 addImageHelper( url, width, height );
481 addImageHelper( url );
484 void TextEdit::addImageHelper(
const KUrl &url,
int width,
int height)
487 if ( !image.load( url.path() ) ) {
488 KMessageBox::error(
this,
490 "Unable to load image <filename>%1</filename>.",
494 QFileInfo fi( url.path() );
495 QString imageName = fi.baseName().isEmpty() ? QLatin1String(
"image.png" )
496 : QString( fi.baseName() + QLatin1String(
".png" ) );
497 d->addImageHelper( imageName, image, width, height );
501 const QString &resourceName )
503 QSet<int> cursorPositionsToSkip;
504 QTextBlock currentBlock = document()->begin();
505 QTextBlock::iterator it;
506 while ( currentBlock.isValid() ) {
507 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
508 QTextFragment fragment = it.fragment();
509 if ( fragment.isValid() ) {
510 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
511 if ( imageFormat.isValid() && imageFormat.name() == matchName ) {
512 int pos = fragment.position();
513 if ( !cursorPositionsToSkip.contains( pos ) ) {
514 QTextCursor cursor( document() );
515 cursor.setPosition( pos );
516 cursor.setPosition( pos + 1, QTextCursor::KeepAnchor );
517 cursor.removeSelectedText();
518 document()->addResource( QTextDocument::ImageResource,
519 QUrl( resourceName ), QVariant( image ) );
520 QTextImageFormat format;
521 format.setName( resourceName );
522 if ( (imageFormat.width()!=0) && (imageFormat.height()!=0) ) {
523 format.setWidth( imageFormat.width() );
524 format.setHeight( imageFormat.height() );
526 cursor.insertImage( format );
532 cursorPositionsToSkip.insert( pos );
533 it = currentBlock.begin();
538 currentBlock = currentBlock.next();
542 void TextEditPrivate::addImageHelper(
const QString &imageName,
const QImage &image,
int width,
int height )
544 QString imageNameToAdd = imageName;
545 QTextDocument *document = q->document();
549 while ( mImageNames.contains( imageNameToAdd ) ) {
550 QVariant qv = document->resource( QTextDocument::ImageResource, QUrl( imageNameToAdd ) );
555 int firstDot = imageName.indexOf( QLatin1Char(
'.' ) );
556 if ( firstDot == -1 ) {
557 imageNameToAdd = imageName + QString::number( imageNumber++ );
559 imageNameToAdd = imageName.left( firstDot ) + QString::number( imageNumber++ ) +
560 imageName.mid( firstDot );
564 if ( !mImageNames.contains( imageNameToAdd ) ) {
565 document->addResource( QTextDocument::ImageResource, QUrl( imageNameToAdd ), image );
566 mImageNames << imageNameToAdd;
568 if ( width != -1 && height != -1 ) {
569 QTextImageFormat format;
570 format.setName( imageNameToAdd );
571 format.setWidth( width );
572 format.setHeight( height );
573 q->textCursor().insertImage( format );
575 q->textCursor().insertImage( imageNameToAdd );
577 q->enableRichTextMode();
582 ImageWithNameList retImages;
583 QStringList seenImageNames;
584 QList<QTextImageFormat> imageFormats = d->embeddedImageFormats();
585 foreach (
const QTextImageFormat &imageFormat, imageFormats ) {
586 if ( !seenImageNames.contains( imageFormat.name() ) ) {
587 QVariant resourceData = document()->resource( QTextDocument::ImageResource,
588 QUrl( imageFormat.name() ) );
589 QImage image = qvariant_cast<QImage>( resourceData );
590 QString name = imageFormat.name();
592 newImage->image = image;
593 newImage->name = name;
594 retImages.append( newImage );
595 seenImageNames.append( imageFormat.name() );
604 QList< QSharedPointer<EmbeddedImage> > retImages;
605 foreach (
const ImageWithNamePtr &normalImage, normalImages ) {
607 buffer.open( QIODevice::WriteOnly );
608 normalImage->image.save( &buffer,
"PNG" );
610 qsrand( QDateTime::currentDateTime().toTime_t() + qHash( normalImage->name ) );
611 QSharedPointer<EmbeddedImage> embeddedImage(
new EmbeddedImage() );
612 retImages.append( embeddedImage );
613 embeddedImage->image = KMime::Codec::codecForName(
"base64" )->encode( buffer.buffer() );
614 embeddedImage->imageName = normalImage->name;
615 embeddedImage->contentID = QString( QLatin1String(
"%1@KDE" ) ).arg( qrand() );
620 QList<QTextImageFormat> TextEditPrivate::embeddedImageFormats()
const
622 QTextDocument *doc = q->document();
623 QList<QTextImageFormat> retList;
625 QTextBlock currentBlock = doc->begin();
626 while ( currentBlock.isValid() ) {
627 QTextBlock::iterator it;
628 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
629 QTextFragment fragment = it.fragment();
630 if ( fragment.isValid() ) {
631 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
632 if ( imageFormat.isValid() ) {
634 QUrl url( imageFormat.name() );
635 if ( !url.isValid() || !url.scheme().startsWith( QLatin1String(
"http" ) ) ) {
636 retList.append( imageFormat );
641 currentBlock = currentBlock.next();
646 void TextEditPrivate::_k_slotAddEmoticon(
const QString& text)
648 QTextCursor cursor = q->textCursor();
649 cursor.insertText( text );
652 void TextEditPrivate::_k_slotInsertHtml()
654 if ( q->textMode() == KRichTextEdit::Rich ) {
655 QPointer<InsertHtmlDialog> dialog =
new InsertHtmlDialog( q );
656 if ( dialog->exec() ) {
657 const QString str = dialog->html();
658 if ( !str.isEmpty() ) {
659 QTextCursor cursor = q->textCursor();
660 cursor.insertHtml( str );
667 void TextEditPrivate::_k_slotAddImage()
669 QPointer<InsertImageDialog> dlg =
new InsertImageDialog( q );
670 if ( dlg->exec() == KDialog::Accepted && dlg ) {
671 const KUrl url = dlg->imageUrl();
673 int imageHeight = -1;
674 if ( !dlg->keepOriginalSize() ) {
675 imageWidth = dlg->imageWidth();
676 imageHeight = dlg->imageHeight();
678 q->addImage( url, imageWidth, imageHeight );
683 void TextEditPrivate::_k_slotTextModeChanged(KRichTextEdit::Mode mode)
685 if(mode == KRichTextEdit::Rich) {
686 saveFont = q->currentFont();
691 void TextEditPrivate::_k_slotFormatReset()
693 q->setTextBackgroundColor( q->palette().highlightedText().color() );
694 q->setTextForegroundColor( q->palette().text().color() );
695 q->setFont( saveFont );
701 d->imageSupportEnabled =
true;
706 return d->imageSupportEnabled;
711 d->emoticonSupportEnabled =
true;
716 return d->emoticonSupportEnabled;
719 void KPIMTextEdit::TextEdit::enableInsertHtmlActions()
721 d->insertHtmlSupportEnabled =
true;
726 return d->insertHtmlSupportEnabled;
731 return d->insertTableSupportEnabled;
734 void KPIMTextEdit::TextEdit::enableInsertTableActions()
736 d->insertTableSupportEnabled =
true;
741 const QByteArray &htmlBody,
const KPIMTextEdit::ImageList &imageList )
743 QByteArray result = htmlBody;
744 if ( !imageList.isEmpty() ) {
745 foreach (
const QSharedPointer<EmbeddedImage> &image, imageList ) {
746 const QString newImageName = QLatin1String(
"cid:" ) + image->contentID;
747 QByteArray quote(
"\"" );
748 result.replace( QByteArray( quote + image->imageName.toLocal8Bit() + quote ),
749 QByteArray( quote + newImageName.toLocal8Bit() + quote ) );
757 QString imageName = fileInfo.baseName().isEmpty() ?
758 i18nc(
"Start of the filename for an image",
"image" ) :
760 d->addImageHelper( imageName, image );
766 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
767 QImage image = qvariant_cast<QImage>( source->imageData() );
768 QFileInfo fi( source->text() );
775 if ( textMode() == KRichTextEdit::Plain && source->hasHtml() ) {
776 if ( source->hasText() ) {
777 insertPlainText( source->text() );
782 KRichTextWidget::insertFromMimeData( source );
787 if ( source->hasHtml() && textMode() == KRichTextEdit::Rich ) {
791 if ( source->hasText() ) {
795 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
799 return KRichTextWidget::canInsertFromMimeData( source );
804 if ( textMode() == Plain ) {
811 void TextEditPrivate::_k_slotDeleteLine()
813 if ( q->hasFocus() ) {
814 q->deleteCurrentLine();
820 QTextCursor cursor = textCursor();
821 QTextBlock block = cursor.block();
822 const QTextLayout *layout = block.layout();
826 for (
int lineNumber = 0; lineNumber < layout->lineCount(); lineNumber++ ) {
827 QTextLine line = layout->lineAt( lineNumber );
828 const bool lastLineInBlock = ( line.textStart() + line.textLength() == block.length() - 1 );
829 const bool oneLineBlock = ( layout->lineCount() == 1 );
830 const int startOfLine = block.position() + line.textStart();
831 int endOfLine = block.position() + line.textStart() + line.textLength();
832 if ( !lastLineInBlock ) {
837 if ( cursor.position() >= startOfLine && cursor.position() <= endOfLine ) {
838 int deleteStart = startOfLine;
839 int deleteLength = line.textLength();
840 if ( oneLineBlock ) {
846 if ( deleteStart + deleteLength >= document()->characterCount() &&
851 cursor.beginEditBlock();
852 cursor.setPosition( deleteStart );
853 cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor, deleteLength );
854 cursor.removeSelectedText();
855 cursor.endEditBlock();
862 #include "moc_textedit.cpp"