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>
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"
59 : actionAddImage( 0 ),
60 actionDeleteLine( 0 ),
61 actionAddEmoticon( 0 ),
62 actionInsertHtml( 0 ),
64 actionFormatReset( 0 ),
66 imageSupportEnabled( false ),
67 emoticonSupportEnabled( false ),
68 insertHtmlSupportEnabled( false ),
69 insertTableSupportEnabled( false ),
70 spellCheckingEnabled( false )
82 void addImageHelper(
const QString &imageName,
const QImage &image,
83 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;
139 QStringList mImageNames;
144 bool imageSupportEnabled;
146 bool emoticonSupportEnabled;
148 bool insertHtmlSupportEnabled;
150 bool insertTableSupportEnabled;
163 bool spellCheckingEnabled;
171 void TextEditPrivate::fixupTextEditString( QString &text )
const
174 text.remove( QChar::LineSeparator );
178 text.remove( 0xFFFC );
181 text.replace( QChar::Nbsp, QChar::fromLatin1(
' ' ) );
185 : KRichTextWidget( text, parent ),
186 d( new TextEditPrivate( this ) )
192 : KRichTextWidget( parent ),
193 d( new TextEditPrivate( this ) )
199 : KRichTextWidget( parent ),
200 d( new TextEditPrivate( this ) )
214 KCursor::autoHideEventFilter( o, e );
217 return KRichTextWidget::eventFilter( o, e );
220 void TextEditPrivate::init()
222 q->connect( q, SIGNAL(textModeChanged(KRichTextEdit::Mode)),
223 q, SLOT(_k_slotTextModeChanged(KRichTextEdit::Mode)) );
224 q->setSpellInterface( q );
232 spellCheckingEnabled =
false;
233 q->setCheckSpellingEnabledInternal(
true );
236 KCursor::setAutoHideCursor( q,
true,
true );
238 q->installEventFilter( q );
243 return d->configFile;
248 if ( e->key() == Qt::Key_Return ) {
249 QTextCursor cursor = textCursor();
250 int oldPos = cursor.position();
251 int blockPos = cursor.block().position();
254 cursor.movePosition( QTextCursor::StartOfBlock );
255 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
256 QString lineText = cursor.selectedText();
257 if ( ( ( oldPos - blockPos ) > 0 ) &&
258 ( ( oldPos - blockPos ) <
int( lineText.length() ) ) ) {
259 bool isQuotedLine =
false;
261 while ( bot < lineText.length() ) {
262 if ( ( lineText[bot] == QChar::fromLatin1(
'>' ) ) ||
263 ( lineText[bot] == QChar::fromLatin1(
'|' ) ) ) {
266 }
else if ( lineText[bot].isSpace() ) {
272 KRichTextWidget::keyPressEvent( e );
277 ( bot != lineText.length() ) &&
278 ( ( oldPos - blockPos ) >=
int( bot ) ) ) {
281 cursor.movePosition( QTextCursor::StartOfBlock );
282 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
283 QString newLine = cursor.selectedText();
287 int leadingWhiteSpaceCount = 0;
288 while ( ( leadingWhiteSpaceCount < newLine.length() ) &&
289 newLine[leadingWhiteSpaceCount].isSpace() ) {
290 ++leadingWhiteSpaceCount;
292 newLine = newLine.replace( 0, leadingWhiteSpaceCount, lineText.left( bot ) );
293 cursor.insertText( newLine );
295 cursor.movePosition( QTextCursor::StartOfBlock );
296 setTextCursor( cursor );
299 KRichTextWidget::keyPressEvent( e );
302 KRichTextWidget::keyPressEvent( e );
308 return d->spellCheckingEnabled;
318 d->spellCheckingEnabled = enable;
319 emit checkSpellingChanged( enable );
329 return quoteLength( line ) > 0;
334 bool quoteFound =
false;
335 int startOfText = -1;
336 const int lineLength( line.length() );
337 for (
int i = 0; i < lineLength; ++i ) {
338 if ( line[i] == QLatin1Char(
'>' ) || line[i] == QLatin1Char(
'|' ) ) {
340 }
else if ( line[i] != QLatin1Char(
' ' ) ) {
346 if ( startOfText == -1 ) {
347 startOfText = line.length() - 1;
357 return QLatin1String(
"> " );
367 KRichTextWidget::setHighlighter( emailHighLighter );
369 if ( !spellCheckingLanguage().isEmpty() ) {
370 setSpellCheckingLanguage( spellCheckingLanguage() );
377 Q_UNUSED( highlighter );
382 QTextDocument *doc = document();
389 QRegExp rx( QLatin1String(
"(http|ftp|ldap)s?\\S+-$" ) );
390 QTextBlock block = doc->begin();
391 while ( block.isValid() ) {
392 QTextLayout *layout = block.layout();
393 const int numberOfLine( layout->lineCount() );
394 bool urlStart =
false;
395 for (
int i = 0; i < numberOfLine; ++i ) {
396 QTextLine line = layout->lineAt( i );
397 QString lineText = block.text().mid( line.textStart(), line.textLength() );
399 if ( lineText.contains( rx ) ||
400 ( urlStart && !lineText.contains( QLatin1Char(
' ' ) ) &&
401 lineText.endsWith( QLatin1Char(
'-' ) ) ) ) {
406 temp += lineText + QLatin1Char(
'\n' );
409 block = block.next();
413 if ( temp.endsWith( QLatin1Char(
'\n' ) ) ) {
417 d->fixupTextEditString( temp );
423 QString temp = plainText;
424 d->fixupTextEditString( temp );
435 KRichTextWidget::createActions( actionCollection );
437 if ( d->imageSupportEnabled ) {
438 d->actionAddImage =
new KAction( KIcon( QLatin1String(
"insert-image" ) ),
439 i18n(
"Add Image" ),
this );
440 actionCollection->addAction( QLatin1String(
"add_image" ), d->actionAddImage );
441 connect( d->actionAddImage, SIGNAL(triggered(
bool)), SLOT(_k_slotAddImage()) );
443 if ( d->emoticonSupportEnabled ) {
444 d->actionAddEmoticon =
new EmoticonTextEditAction(
this );
445 actionCollection->addAction( QLatin1String(
"add_emoticon" ), d->actionAddEmoticon );
446 connect( d->actionAddEmoticon, SIGNAL(emoticonActivated(QString)),
447 SLOT(_k_slotAddEmoticon(QString)) );
450 if ( d->insertHtmlSupportEnabled ) {
451 d->actionInsertHtml =
new KAction( i18n(
"Insert HTML" ),
this );
452 actionCollection->addAction( QLatin1String(
"insert_html" ), d->actionInsertHtml );
453 connect( d->actionInsertHtml, SIGNAL(triggered(
bool)), SLOT(_k_slotInsertHtml()) );
456 if ( d->insertTableSupportEnabled ) {
457 d->actionTable =
new TableActionMenu( actionCollection,
this );
458 d->actionTable->setIcon( KIcon( QLatin1String(
"insert-table" ) ) );
459 d->actionTable->setText( i18n(
"Table" ) );
460 d->actionTable->setDelayed(
false );
461 actionCollection->addAction( QLatin1String(
"insert_table" ), d->actionTable );
464 d->actionDeleteLine =
new KAction( i18n(
"Delete Line" ),
this );
465 d->actionDeleteLine->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_K ) );
466 actionCollection->addAction( QLatin1String(
"delete_line" ), d->actionDeleteLine );
467 connect( d->actionDeleteLine, SIGNAL(triggered(
bool)), SLOT(_k_slotDeleteLine()) );
469 d->actionFormatReset =
470 new KAction( KIcon( QLatin1String(
"draw-eraser" ) ), i18n(
"Reset Font Settings" ),
this );
471 d->actionFormatReset->setIconText( i18n(
"Reset Font" ) );
472 actionCollection->addAction( QLatin1String(
"format_reset" ), d->actionFormatReset );
473 connect( d->actionFormatReset, SIGNAL(triggered(
bool)), SLOT(_k_slotFormatReset()) );
478 addImageHelper( url, width, height );
483 addImageHelper( url );
486 void TextEdit::addImageHelper(
const KUrl &url,
int width,
int height )
489 if ( !image.load( url.path() ) ) {
493 "Unable to load image <filename>%1</filename>.",
497 QFileInfo fi( url.path() );
499 fi.baseName().isEmpty() ?
500 QLatin1String(
"image.png" ) :
501 QString( fi.baseName() + QLatin1String(
".png" ) );
502 d->addImageHelper( imageName, image, width, height );
506 const QString &resourceName )
508 QSet<int> cursorPositionsToSkip;
509 QTextBlock currentBlock = document()->begin();
510 QTextBlock::iterator it;
511 while ( currentBlock.isValid() ) {
512 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
513 QTextFragment fragment = it.fragment();
514 if ( fragment.isValid() ) {
515 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
516 if ( imageFormat.isValid() && imageFormat.name() == matchName ) {
517 int pos = fragment.position();
518 if ( !cursorPositionsToSkip.contains( pos ) ) {
519 QTextCursor cursor( document() );
520 cursor.setPosition( pos );
521 cursor.setPosition( pos + 1, QTextCursor::KeepAnchor );
522 cursor.removeSelectedText();
523 document()->addResource( QTextDocument::ImageResource,
524 QUrl( resourceName ), QVariant( image ) );
525 QTextImageFormat format;
526 format.setName( resourceName );
527 if ( ( imageFormat.width() != 0 ) && ( imageFormat.height() != 0 ) ) {
528 format.setWidth( imageFormat.width() );
529 format.setHeight( imageFormat.height() );
531 cursor.insertImage( format );
536 cursorPositionsToSkip.insert( pos );
537 it = currentBlock.begin();
542 currentBlock = currentBlock.next();
546 void TextEditPrivate::addImageHelper(
const QString &imageName,
const QImage &image,
547 int width,
int height )
549 QString imageNameToAdd = imageName;
550 QTextDocument *document = q->document();
554 while ( mImageNames.contains( imageNameToAdd ) ) {
555 QVariant qv = document->resource( QTextDocument::ImageResource, QUrl( imageNameToAdd ) );
560 int firstDot = imageName.indexOf( QLatin1Char(
'.' ) );
561 if ( firstDot == -1 ) {
562 imageNameToAdd = imageName + QString::number( imageNumber++ );
564 imageNameToAdd = imageName.left( firstDot ) + QString::number( imageNumber++ ) +
565 imageName.mid( firstDot );
569 if ( !mImageNames.contains( imageNameToAdd ) ) {
570 document->addResource( QTextDocument::ImageResource, QUrl( imageNameToAdd ), image );
571 mImageNames << imageNameToAdd;
573 if ( width != -1 && height != -1 ) {
574 QTextImageFormat format;
575 format.setName( imageNameToAdd );
576 format.setWidth( width );
577 format.setHeight( height );
578 q->textCursor().insertImage( format );
580 q->textCursor().insertImage( imageNameToAdd );
582 q->enableRichTextMode();
587 ImageWithNameList retImages;
588 QStringList seenImageNames;
589 QList<QTextImageFormat> imageFormats = d->embeddedImageFormats();
590 foreach (
const QTextImageFormat &imageFormat, imageFormats ) {
591 if ( !seenImageNames.contains( imageFormat.name() ) ) {
592 QVariant resourceData = document()->resource( QTextDocument::ImageResource,
593 QUrl( imageFormat.name() ) );
594 QImage image = qvariant_cast<QImage>( resourceData );
595 QString name = imageFormat.name();
597 newImage->image = image;
598 newImage->name = name;
599 retImages.append( newImage );
600 seenImageNames.append( imageFormat.name() );
609 QList< QSharedPointer<EmbeddedImage> > retImages;
610 foreach (
const ImageWithNamePtr &normalImage, normalImages ) {
612 buffer.open( QIODevice::WriteOnly );
613 normalImage->image.save( &buffer,
"PNG" );
615 qsrand( QDateTime::currentDateTime().toTime_t() + qHash( normalImage->name ) );
616 QSharedPointer<EmbeddedImage> embeddedImage(
new EmbeddedImage() );
617 retImages.append( embeddedImage );
618 embeddedImage->image = KMime::Codec::codecForName(
"base64" )->encode( buffer.buffer() );
619 embeddedImage->imageName = normalImage->name;
620 embeddedImage->contentID = QString( QLatin1String(
"%1@KDE" ) ).arg( qrand() );
625 QList<QTextImageFormat> TextEditPrivate::embeddedImageFormats()
const
627 QTextDocument *doc = q->document();
628 QList<QTextImageFormat> retList;
630 QTextBlock currentBlock = doc->begin();
631 while ( currentBlock.isValid() ) {
632 QTextBlock::iterator it;
633 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
634 QTextFragment fragment = it.fragment();
635 if ( fragment.isValid() ) {
636 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
637 if ( imageFormat.isValid() ) {
639 QUrl url( imageFormat.name() );
640 if ( !url.isValid() || !url.scheme().startsWith( QLatin1String(
"http" ) ) ) {
641 retList.append( imageFormat );
646 currentBlock = currentBlock.next();
651 void TextEditPrivate::_k_slotAddEmoticon(
const QString &text )
653 QTextCursor cursor = q->textCursor();
654 cursor.insertText( text );
657 void TextEditPrivate::_k_slotInsertHtml()
659 if ( q->textMode() == KRichTextEdit::Rich ) {
660 QPointer<InsertHtmlDialog> dialog =
new InsertHtmlDialog( q );
661 if ( dialog->exec() ) {
662 const QString str = dialog->html();
663 if ( !str.isEmpty() ) {
664 QTextCursor cursor = q->textCursor();
665 cursor.insertHtml( str );
672 void TextEditPrivate::_k_slotAddImage()
674 QPointer<InsertImageDialog> dlg =
new InsertImageDialog( q );
675 if ( dlg->exec() == KDialog::Accepted && dlg ) {
676 const KUrl url = dlg->imageUrl();
678 int imageHeight = -1;
679 if ( !dlg->keepOriginalSize() ) {
680 imageWidth = dlg->imageWidth();
681 imageHeight = dlg->imageHeight();
683 q->addImage( url, imageWidth, imageHeight );
688 void TextEditPrivate::_k_slotTextModeChanged( KRichTextEdit::Mode mode )
690 if ( mode == KRichTextEdit::Rich ) {
691 saveFont = q->currentFont();
695 void TextEditPrivate::_k_slotFormatReset()
697 q->setTextBackgroundColor( q->palette().highlightedText().color() );
698 q->setTextForegroundColor( q->palette().text().color() );
699 q->setFont( saveFont );
705 d->imageSupportEnabled =
true;
710 return d->imageSupportEnabled;
715 d->emoticonSupportEnabled =
true;
720 return d->emoticonSupportEnabled;
723 void KPIMTextEdit::TextEdit::enableInsertHtmlActions()
725 d->insertHtmlSupportEnabled =
true;
730 return d->insertHtmlSupportEnabled;
735 return d->insertTableSupportEnabled;
738 void KPIMTextEdit::TextEdit::enableInsertTableActions()
740 d->insertTableSupportEnabled =
true;
744 const QByteArray &htmlBody,
const KPIMTextEdit::ImageList &imageList )
746 QByteArray result = htmlBody;
747 if ( !imageList.isEmpty() ) {
748 foreach (
const QSharedPointer<EmbeddedImage> &image, imageList ) {
749 const QString newImageName = QLatin1String(
"cid:" ) + image->contentID;
750 QByteArray quote(
"\"" );
751 result.replace( QByteArray( quote + image->imageName.toLocal8Bit() + quote ),
752 QByteArray( quote + newImageName.toLocal8Bit() + quote ) );
760 QString imageName = fileInfo.baseName().isEmpty() ?
761 i18nc(
"Start of the filename for an image",
"image" ) :
763 d->addImageHelper( imageName, image );
769 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
770 QImage image = qvariant_cast<QImage>( source->imageData() );
778 if ( textMode() == KRichTextEdit::Plain && source->hasHtml() ) {
779 if ( source->hasText() ) {
780 insertPlainText( source->text() );
785 KRichTextWidget::insertFromMimeData( source );
790 if ( source->hasHtml() && textMode() == KRichTextEdit::Rich ) {
794 if ( source->hasText() ) {
798 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
802 return KRichTextWidget::canInsertFromMimeData( source );
807 if ( textMode() == Plain ) {
814 void TextEditPrivate::_k_slotDeleteLine()
816 if ( q->hasFocus() ) {
817 q->deleteCurrentLine();
823 QTextCursor cursor = textCursor();
824 QTextBlock block = cursor.block();
825 const QTextLayout *layout = block.layout();
829 for (
int lineNumber = 0; lineNumber < layout->lineCount(); lineNumber++ ) {
830 QTextLine line = layout->lineAt( lineNumber );
831 const bool lastLineInBlock = ( line.textStart() + line.textLength() == block.length() - 1 );
832 const bool oneLineBlock = ( layout->lineCount() == 1 );
833 const int startOfLine = block.position() + line.textStart();
834 int endOfLine = block.position() + line.textStart() + line.textLength();
835 if ( !lastLineInBlock ) {
840 if ( cursor.position() >= startOfLine && cursor.position() <= endOfLine ) {
841 int deleteStart = startOfLine;
842 int deleteLength = line.textLength();
843 if ( oneLineBlock ) {
849 if ( deleteStart + deleteLength >= document()->characterCount() &&
854 cursor.beginEditBlock();
855 cursor.setPosition( deleteStart );
856 cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor, deleteLength );
857 cursor.removeSelectedText();
858 cursor.endEditBlock();
864 #include "moc_textedit.cpp"