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"
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.