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() );
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"
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.
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.