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>
33#include <KDE/KActionCollection>
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>
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;
171void 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 );
220void 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 );
486void 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();
546void 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 );
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() );
625QList<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();
651void TextEditPrivate::_k_slotAddEmoticon(
const QString &text )
653 QTextCursor cursor = q->textCursor();
654 cursor.insertText( text );
657void 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 );
672void 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 );
688void TextEditPrivate::_k_slotTextModeChanged( KRichTextEdit::Mode mode )
690 if ( mode == KRichTextEdit::Rich ) {
691 saveFont = q->currentFont();
695void 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;
723void KPIMTextEdit::TextEdit::enableInsertHtmlActions()
725 d->insertHtmlSupportEnabled =
true;
730 return d->insertHtmlSupportEnabled;
735 return d->insertTableSupportEnabled;
738void 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 ) {
814void TextEditPrivate::_k_slotDeleteLine()
816 if ( q->hasFocus() ) {
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"
This highlighter highlights spelling mistakes and also highlightes quotes.
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...
virtual int quoteLength(const QString &line) const
This is called whenever the editor needs to find out the length of the quote, i.e.
bool isEnableImageActions() const
Return true if richtext mode support image.
virtual void createActions(KActionCollection *actionCollection)
Reimplemented from KMEditor, to support more actions.
virtual void setSpellCheckingEnabled(bool enable)
Reimplemented from KTextEditSpellInterface.
virtual void createHighlighter()
Reimplemented to create our own highlighter which does quote and spellcheck highlighting.
virtual bool shouldBlockBeSpellChecked(const QString &block) const
Reimplemented from KTextEditSpellInterface, to avoid spellchecking quoted text.
bool isEnableInsertHtmlActions() const
virtual bool canInsertFromMimeData(const QMimeData *source) const
Reimplemented for inline image support.
virtual bool isSpellCheckingEnabled() const
Reimplemented from KTextEditSpellInterface.
virtual void insertFromMimeData(const QMimeData *source)
Reimplemented for inline image support.
bool isFormattingUsed() const
Checks if rich text formatting is used anywhere.
void enableEmoticonActions()
Calling this allows createActions() to create the add emoticons actions.
bool isEnableEmoticonActions() const
Return true if emoticons actions supported.
void loadImage(const QImage &image, const QString &matchName, const QString &resourceName)
Loads an image into the textedit.
TextEdit(const QString &text, QWidget *parent=0)
Constructs a TextEdit object.
QString configFile() const
Return config file.
void addImage(const KUrl &url)
Adds an image.
virtual bool eventFilter(QObject *o, QEvent *e)
Reimplemented from KRichTextWidget to hide the mouse cursor when there was no mouse movement for some...
static QByteArray imageNamesToContentIds(const QByteArray &htmlBody, const ImageList &imageList)
For all given embedded images, this function replace the image name in the.
bool isLineQuoted(const QString &line) const
Convenience method for qouteLength( line ) > 0.
bool isEnableInsertTableActions() const
ImageList embeddedImages() const
Get a list with all embedded HTML images.
void insertImage(const QImage &image, const QFileInfo &info)
void deleteCurrentLine()
Deletes the line at the current cursor position.
virtual void keyPressEvent(QKeyEvent *e)
Reimplemented to add qoute signs when the user presses enter on a quoted line.
virtual const QString defaultQuoteSign() const
Returns the prefix that is added to a line that is quoted.
void enableImageActions()
Calling this allows createActions() to create the add image actions.
ImageWithNameList imagesWithName() const
Same as embeddedImages(), only that this returns a list of general purpose information,...
virtual void setHighlighterColors(EMailQuoteHighlighter *highlighter)
This method is called after the highlighter is created.
QString toWrappedPlainText() const
Returns the text of the editor as plain text, with linebreaks inserted where word-wrapping occurred.
QString toCleanPlainText() const
Same as toPlainText() from QTextEdit, only that it removes embedded images and converts non-breaking ...
KPIMTEXTEDIT_EXPORT bool containsFormatting(const QTextDocument *document)
Returns whether the QTextDocument document contains rich text formatting.
Copyright (C) 2006 Laurent Montel montel@kde.org Copyright (C) 2008 Thomas McGuire mcguire@kde....
Holds information about an embedded HTML image that will be useful for mail clients.
Holds information about an embedded HTML image that will be generally useful.