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;
135 bool imageSupportEnabled;
137 bool emoticonSupportEnabled;
139 bool insertHtmlSupportEnabled;
141 bool insertTableSupportEnabled;
147 QStringList mImageNames;
160 bool spellCheckingEnabled;
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)),
222 q, SLOT(_k_slotTextModeChanged(KRichTextEdit::Mode)) );
223 q->setSpellInterface( q );
231 spellCheckingEnabled =
false;
232 q->setCheckSpellingEnabledInternal(
true );
235 KCursor::setAutoHideCursor( q,
true,
true );
237 q->installEventFilter( q );
242 return d->configFile;
247 if ( e->key() == Qt::Key_Return ) {
248 QTextCursor cursor = textCursor();
249 int oldPos = cursor.position();
250 int blockPos = cursor.block().position();
253 cursor.movePosition( QTextCursor::StartOfBlock );
254 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
255 QString lineText = cursor.selectedText();
256 if ( ( ( oldPos - blockPos ) > 0 ) &&
257 ( ( oldPos - blockPos ) <
int( lineText.length() ) ) ) {
258 bool isQuotedLine =
false;
260 while ( bot < lineText.length() ) {
261 if ( ( lineText[bot] == QChar::fromLatin1(
'>' ) ) ||
262 ( lineText[bot] == QChar::fromLatin1(
'|' ) ) ) {
265 }
else if ( lineText[bot].isSpace() ) {
271 KRichTextWidget::keyPressEvent( e );
276 ( bot != lineText.length() ) &&
277 ( ( oldPos - blockPos ) >= int( bot ) ) ) {
280 cursor.movePosition( QTextCursor::StartOfBlock );
281 cursor.movePosition( QTextCursor::EndOfBlock, QTextCursor::KeepAnchor );
282 QString newLine = cursor.selectedText();
286 int leadingWhiteSpaceCount = 0;
287 while ( ( leadingWhiteSpaceCount < newLine.length() ) &&
288 newLine[leadingWhiteSpaceCount].isSpace() ) {
289 ++leadingWhiteSpaceCount;
291 newLine = newLine.replace( 0, leadingWhiteSpaceCount, lineText.left( bot ) );
292 cursor.insertText( newLine );
294 cursor.movePosition( QTextCursor::StartOfBlock );
295 setTextCursor( cursor );
298 KRichTextWidget::keyPressEvent( e );
301 KRichTextWidget::keyPressEvent( e );
307 return d->spellCheckingEnabled;
317 d->spellCheckingEnabled = enable;
318 emit checkSpellingChanged( enable );
328 return quoteLength( line ) > 0;
333 bool quoteFound =
false;
334 int startOfText = -1;
335 const int lineLength( line.length() );
336 for (
int i = 0; i < lineLength; ++i ) {
337 if ( line[i] == QLatin1Char(
'>' ) || line[i] == QLatin1Char(
'|' ) ) {
339 }
else if ( line[i] != QLatin1Char(
' ' ) ) {
345 if ( startOfText == -1 ) {
346 startOfText = line.length() - 1;
356 return QLatin1String(
"> " );
366 KRichTextWidget::setHighlighter( emailHighLighter );
368 if ( !spellCheckingLanguage().isEmpty() ) {
369 setSpellCheckingLanguage( spellCheckingLanguage() );
376 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 ) ||
399 ( urlStart && !lineText.contains( QLatin1Char(
' ' ) ) &&
400 lineText.endsWith( QLatin1Char(
'-' ) ) ) ) {
405 temp += lineText + QLatin1Char(
'\n' );
408 block = block.next();
412 if ( temp.endsWith( QLatin1Char(
'\n' ) ) ) {
416 d->fixupTextEditString( temp );
422 QString temp = plainText;
423 d->fixupTextEditString( temp );
434 KRichTextWidget::createActions( actionCollection );
436 if ( d->imageSupportEnabled ) {
437 d->actionAddImage =
new KAction( KIcon( QLatin1String(
"insert-image" ) ),
438 i18n(
"Add Image" ),
this );
439 actionCollection->addAction( QLatin1String(
"add_image" ), d->actionAddImage );
440 connect( d->actionAddImage, SIGNAL(triggered(
bool)), SLOT(_k_slotAddImage()) );
442 if ( d->emoticonSupportEnabled ) {
443 d->actionAddEmoticon =
new EmoticonTextEditAction(
this );
444 actionCollection->addAction( QLatin1String(
"add_emoticon" ), d->actionAddEmoticon );
445 connect( d->actionAddEmoticon, SIGNAL(emoticonActivated(QString)),
446 SLOT(_k_slotAddEmoticon(QString)) );
449 if ( d->insertHtmlSupportEnabled ) {
450 d->actionInsertHtml =
new KAction( i18n(
"Insert HTML" ),
this );
451 actionCollection->addAction( QLatin1String(
"insert_html" ), d->actionInsertHtml );
452 connect( d->actionInsertHtml, SIGNAL(triggered(
bool)), SLOT(_k_slotInsertHtml()) );
455 if ( d->insertTableSupportEnabled ) {
456 d->actionTable =
new TableActionMenu( actionCollection,
this );
457 d->actionTable->setIcon( KIcon( QLatin1String(
"insert-table" ) ) );
458 d->actionTable->setText( i18n(
"Table" ) );
459 d->actionTable->setDelayed(
false );
460 actionCollection->addAction( QLatin1String(
"insert_table" ), d->actionTable );
463 d->actionDeleteLine =
new KAction( i18n(
"Delete Line" ),
this );
464 d->actionDeleteLine->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_K ) );
465 actionCollection->addAction( QLatin1String(
"delete_line" ), d->actionDeleteLine );
466 connect( d->actionDeleteLine, SIGNAL(triggered(
bool)), SLOT(_k_slotDeleteLine()) );
468 d->actionFormatReset =
469 new KAction( KIcon( QLatin1String(
"draw-eraser" ) ), i18n(
"Reset Font Settings" ),
this );
470 d->actionFormatReset->setIconText( i18n(
"Reset Font" ) );
471 actionCollection->addAction( QLatin1String(
"format_reset" ), d->actionFormatReset );
472 connect( d->actionFormatReset, SIGNAL(triggered(
bool)), SLOT(_k_slotFormatReset()) );
477 addImageHelper( url, width, height );
482 addImageHelper( url );
485 void TextEdit::addImageHelper(
const KUrl &url,
int width,
int height )
488 if ( !image.load( url.path() ) ) {
492 "Unable to load image <filename>%1</filename>.",
496 QFileInfo fi( url.path() );
498 fi.baseName().isEmpty() ?
499 QLatin1String(
"image.png" ) :
500 QString( fi.baseName() + QLatin1String(
".png" ) );
501 d->addImageHelper( imageName, image, width, height );
505 const QString &resourceName )
507 QSet<int> cursorPositionsToSkip;
508 QTextBlock currentBlock = document()->begin();
509 QTextBlock::iterator it;
510 while ( currentBlock.isValid() ) {
511 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
512 QTextFragment fragment = it.fragment();
513 if ( fragment.isValid() ) {
514 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
515 if ( imageFormat.isValid() && imageFormat.name() == matchName ) {
516 int pos = fragment.position();
517 if ( !cursorPositionsToSkip.contains( pos ) ) {
518 QTextCursor cursor( document() );
519 cursor.setPosition( pos );
520 cursor.setPosition( pos + 1, QTextCursor::KeepAnchor );
521 cursor.removeSelectedText();
522 document()->addResource( QTextDocument::ImageResource,
523 QUrl( resourceName ), QVariant( image ) );
524 QTextImageFormat format;
525 format.setName( resourceName );
526 if ( ( imageFormat.width() != 0 ) && ( imageFormat.height() != 0 ) ) {
527 format.setWidth( imageFormat.width() );
528 format.setHeight( imageFormat.height() );
530 cursor.insertImage( format );
535 cursorPositionsToSkip.insert( pos );
536 it = currentBlock.begin();
541 currentBlock = currentBlock.next();
545 void TextEditPrivate::addImageHelper(
const QString &imageName,
const QImage &image,
546 int width,
int height )
548 QString imageNameToAdd = imageName;
549 QTextDocument *document = q->document();
553 while ( mImageNames.contains( imageNameToAdd ) ) {
554 QVariant qv = document->resource( QTextDocument::ImageResource, QUrl( imageNameToAdd ) );
559 int firstDot = imageName.indexOf( QLatin1Char(
'.' ) );
560 if ( firstDot == -1 ) {
561 imageNameToAdd = imageName + QString::number( imageNumber++ );
563 imageNameToAdd = imageName.left( firstDot ) + QString::number( imageNumber++ ) +
564 imageName.mid( firstDot );
568 if ( !mImageNames.contains( imageNameToAdd ) ) {
569 document->addResource( QTextDocument::ImageResource, QUrl( imageNameToAdd ), image );
570 mImageNames << imageNameToAdd;
572 if ( width != -1 && height != -1 ) {
573 QTextImageFormat format;
574 format.setName( imageNameToAdd );
575 format.setWidth( width );
576 format.setHeight( height );
577 q->textCursor().insertImage( format );
579 q->textCursor().insertImage( imageNameToAdd );
581 q->enableRichTextMode();
586 ImageWithNameList retImages;
587 QStringList seenImageNames;
588 QList<QTextImageFormat> imageFormats = d->embeddedImageFormats();
589 foreach (
const QTextImageFormat &imageFormat, imageFormats ) {
590 if ( !seenImageNames.contains( imageFormat.name() ) ) {
591 QVariant resourceData = document()->resource( QTextDocument::ImageResource,
592 QUrl( imageFormat.name() ) );
593 QImage image = qvariant_cast<QImage>( resourceData );
594 QString name = imageFormat.name();
596 newImage->image = image;
597 newImage->name = name;
598 retImages.append( newImage );
599 seenImageNames.append( imageFormat.name() );
608 QList< QSharedPointer<EmbeddedImage> > retImages;
609 foreach (
const ImageWithNamePtr &normalImage, normalImages ) {
611 buffer.open( QIODevice::WriteOnly );
612 normalImage->image.save( &buffer,
"PNG" );
614 qsrand( QDateTime::currentDateTime().toTime_t() + qHash( normalImage->name ) );
615 QSharedPointer<EmbeddedImage> embeddedImage(
new EmbeddedImage() );
616 retImages.append( embeddedImage );
617 embeddedImage->image = KMime::Codec::codecForName(
"base64" )->encode( buffer.buffer() );
618 embeddedImage->imageName = normalImage->name;
619 embeddedImage->contentID = QString( QLatin1String(
"%1@KDE" ) ).arg( qrand() );
624 QList<QTextImageFormat> TextEditPrivate::embeddedImageFormats()
const
626 QTextDocument *doc = q->document();
627 QList<QTextImageFormat> retList;
629 QTextBlock currentBlock = doc->begin();
630 while ( currentBlock.isValid() ) {
631 QTextBlock::iterator it;
632 for ( it = currentBlock.begin(); !it.atEnd(); ++it ) {
633 QTextFragment fragment = it.fragment();
634 if ( fragment.isValid() ) {
635 QTextImageFormat imageFormat = fragment.charFormat().toImageFormat();
636 if ( imageFormat.isValid() ) {
638 QUrl url( imageFormat.name() );
639 if ( !url.isValid() || !url.scheme().startsWith( QLatin1String(
"http" ) ) ) {
640 retList.append( imageFormat );
645 currentBlock = currentBlock.next();
650 void TextEditPrivate::_k_slotAddEmoticon(
const QString &text )
652 QTextCursor cursor = q->textCursor();
653 cursor.insertText( text );
656 void TextEditPrivate::_k_slotInsertHtml()
658 if ( q->textMode() == KRichTextEdit::Rich ) {
659 QPointer<InsertHtmlDialog> dialog =
new InsertHtmlDialog( q );
660 if ( dialog->exec() ) {
661 const QString str = dialog->html();
662 if ( !str.isEmpty() ) {
663 QTextCursor cursor = q->textCursor();
664 cursor.insertHtml( str );
671 void TextEditPrivate::_k_slotAddImage()
673 QPointer<InsertImageDialog> dlg =
new InsertImageDialog( q );
674 if ( dlg->exec() == KDialog::Accepted && dlg ) {
675 const KUrl url = dlg->imageUrl();
677 int imageHeight = -1;
678 if ( !dlg->keepOriginalSize() ) {
679 imageWidth = dlg->imageWidth();
680 imageHeight = dlg->imageHeight();
682 q->addImage( url, imageWidth, imageHeight );
687 void TextEditPrivate::_k_slotTextModeChanged( KRichTextEdit::Mode mode )
689 if ( mode == KRichTextEdit::Rich ) {
690 saveFont = q->currentFont();
694 void TextEditPrivate::_k_slotFormatReset()
696 q->setTextBackgroundColor( q->palette().highlightedText().color() );
697 q->setTextForegroundColor( q->palette().text().color() );
698 q->setFont( saveFont );
704 d->imageSupportEnabled =
true;
709 return d->imageSupportEnabled;
714 d->emoticonSupportEnabled =
true;
719 return d->emoticonSupportEnabled;
722 void KPIMTextEdit::TextEdit::enableInsertHtmlActions()
724 d->insertHtmlSupportEnabled =
true;
729 return d->insertHtmlSupportEnabled;
734 return d->insertTableSupportEnabled;
737 void KPIMTextEdit::TextEdit::enableInsertTableActions()
739 d->insertTableSupportEnabled =
true;
743 const QByteArray &htmlBody,
const KPIMTextEdit::ImageList &imageList )
745 QByteArray result = htmlBody;
746 if ( !imageList.isEmpty() ) {
747 foreach (
const QSharedPointer<EmbeddedImage> &image, imageList ) {
748 const QString newImageName = QLatin1String(
"cid:" ) + image->contentID;
749 QByteArray quote(
"\"" );
750 result.replace( QByteArray( quote + image->imageName.toLocal8Bit() + quote ),
751 QByteArray( quote + newImageName.toLocal8Bit() + quote ) );
759 QString imageName = fileInfo.baseName().isEmpty() ?
760 i18nc(
"Start of the filename for an image",
"image" ) :
762 d->addImageHelper( imageName, image );
768 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
769 QImage image = qvariant_cast<QImage>( source->imageData() );
777 if ( textMode() == KRichTextEdit::Plain && source->hasHtml() ) {
778 if ( source->hasText() ) {
779 insertPlainText( source->text() );
784 KRichTextWidget::insertFromMimeData( source );
789 if ( source->hasHtml() && textMode() == KRichTextEdit::Rich ) {
793 if ( source->hasText() ) {
797 if ( textMode() == KRichTextEdit::Rich && source->hasImage() && d->imageSupportEnabled ) {
801 return KRichTextWidget::canInsertFromMimeData( source );
806 if ( textMode() == Plain ) {
813 void TextEditPrivate::_k_slotDeleteLine()
815 if ( q->hasFocus() ) {
816 q->deleteCurrentLine();
822 QTextCursor cursor = textCursor();
823 QTextBlock block = cursor.block();
824 const QTextLayout *layout = block.layout();
828 for (
int lineNumber = 0; lineNumber < layout->lineCount(); lineNumber++ ) {
829 QTextLine line = layout->lineAt( lineNumber );
830 const bool lastLineInBlock = ( line.textStart() + line.textLength() == block.length() - 1 );
831 const bool oneLineBlock = ( layout->lineCount() == 1 );
832 const int startOfLine = block.position() + line.textStart();
833 int endOfLine = block.position() + line.textStart() + line.textLength();
834 if ( !lastLineInBlock ) {
839 if ( cursor.position() >= startOfLine && cursor.position() <= endOfLine ) {
840 int deleteStart = startOfLine;
841 int deleteLength = line.textLength();
842 if ( oneLineBlock ) {
848 if ( deleteStart + deleteLength >= document()->characterCount() &&
853 cursor.beginEditBlock();
854 cursor.setPosition( deleteStart );
855 cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor, deleteLength );
856 cursor.removeSelectedText();
857 cursor.endEditBlock();
863 #include "moc_textedit.cpp"
virtual void setHighlighterColors(EMailQuoteHighlighter *highlighter)
This method is called after the highlighter is created.
Holds information about an embedded HTML image that will be useful for mail clients.
void insertImage(const QImage &image, const QFileInfo &info)
virtual bool canInsertFromMimeData(const QMimeData *source) const
Reimplemented for inline image support.
Copyright (C) 2006 Laurent Montel montel@kde.org Copyright (C) 2008 Thomas McGuire mcguire@kde...
virtual void insertFromMimeData(const QMimeData *source)
Reimplemented for inline image support.
bool isLineQuoted(const QString &line) const
Convenience method for qouteLength( line ) > 0.
bool isEnableImageActions() const
Return true if richtext mode support image.
void enableImageActions()
Calling this allows createActions() to create the add image actions.
QString toWrappedPlainText() const
Returns the text of the editor as plain text, with linebreaks inserted where word-wrapping occurred...
ImageList embeddedImages() const
Get a list with all embedded HTML images.
void loadImage(const QImage &image, const QString &matchName, const QString &resourceName)
Loads an image into the textedit.
virtual void createHighlighter()
Reimplemented to create our own highlighter which does quote and spellcheck highlighting.
virtual int quoteLength(const QString &line) const
This is called whenever the editor needs to find out the length of the quote, i.e.
virtual bool eventFilter(QObject *o, QEvent *e)
Reimplemented from KRichTextWidget to hide the mouse cursor when there was no mouse movement for some...
TextEdit(const QString &text, QWidget *parent=0)
Constructs a TextEdit object.
virtual void setSpellCheckingEnabled(bool enable)
Reimplemented from KTextEditSpellInterface.
bool isEnableInsertHtmlActions() const
void deleteCurrentLine()
Deletes the line at the current cursor position.
virtual void createActions(KActionCollection *actionCollection)
Reimplemented from KMEditor, to support more actions.
bool isFormattingUsed() const
Checks if rich text formatting is used anywhere.
This highlighter highlights spelling mistakes and also highlightes quotes.
virtual bool shouldBlockBeSpellChecked(const QString &block) const
Reimplemented from KTextEditSpellInterface, to avoid spellchecking quoted text.
virtual bool isSpellCheckingEnabled() const
Reimplemented from KTextEditSpellInterface.
KPIMTEXTEDIT_EXPORT bool containsFormatting(const QTextDocument *document)
Returns whether the QTextDocument document contains rich text formatting.
void toggleSpellHighlighting(bool on)
Turns spellcheck highlighting on or off.
Special textedit that provides additional features which are useful for PIM applications like mail cl...
Holds information about an embedded HTML image that will be generally useful.
QString toCleanPlainText() const
Same as toPlainText() from QTextEdit, only that it removes embedded images and converts non-breaking ...
virtual const QString defaultQuoteSign() const
Returns the prefix that is added to a line that is quoted.
bool isEnableInsertTableActions() const
ImageWithNameList imagesWithName() const
Same as embeddedImages(), only that this returns a list of general purpose information, whereas the embeddedImages() function returns a list with mail-specific information.
void addImage(const KUrl &url)
Adds an image.
virtual void keyPressEvent(QKeyEvent *e)
Reimplemented to add qoute signs when the user presses enter on a quoted line.
QString configFile() const
Return config file.
bool isEnableEmoticonActions() const
Return true if emoticons actions supported.
void enableEmoticonActions()
Calling this allows createActions() to create the add emoticons actions.
static QByteArray imageNamesToContentIds(const QByteArray &htmlBody, const ImageList &imageList)
For all given embedded images, this function replace the image name in the.