/*
 * kmeditor.cpp
 *
 * Copyright 2007-2015 Laurent Montel <montel@kde.org>
 * Copyright 2008 Thomas McGuire <mcguire@kde.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "kmeditor.h"
#include "part/textpart.h"
#include "messageviewer/viewer/nodehelper.h"
#include "pimcommon/autocorrection/autocorrection.h"
#include "settings/messagecomposersettings.h"

#include <kmacroexpander.h>
#include <KShell>


#include <grantlee/plaintextmarkupbuilder.h>

#include <KEncodingFileDialog>
#include <KLocalizedString>
#include <KMessageBox>
#include <KProcess>
#include <KPushButton>
#include <KTemporaryFile>
#include <KColorScheme>

#include <QApplication>
#include <QClipboard>
#include <QKeyEvent>
#include <QPointer>
#include <QProcess>
#include <QShortcut>
#include <QTextLayout>
#include <QTimer>
#include <QDebug>

using namespace KPIMTextEdit;

namespace MessageComposer {

class KMeditorPrivate
{
public:
    KMeditorPrivate( KMeditor *parent )
        : q( parent ),
          useExtEditor( false ),
          forcePlainTextMarkup( false ),
          mExtEditorProcess( 0 ),
          mExtEditorTempFile( 0 ),
          mAutoCorrection( 0 )
    {
    }

    ~KMeditorPrivate()
    {
    }

    //
    // Slots
    //

    // Just calls KTextEdit::ensureCursorVisible(), workaround for some bug.
    void ensureCursorVisibleDelayed();

    //
    // Normal functions
    //

    void init();
    QString addQuotesToText( const QString &inputText );

    void startExternalEditor();
    void slotEditorFinished( int, QProcess::ExitStatus exitStatus );

    /**
     * Replaces each text which matches the regular expression with another text.
     * Text inside quotes or the given signature will be ignored.
     */
    void cleanWhitespaceHelper( const QRegExp &regExp, const QString &newText,
                                const KPIMIdentities::Signature &sig );

    /**
     * Returns a list of all occurrences of the given signature.
     * The list contains pairs which consists of the starting position and
     * the end of the signature.
     *
     * @param sig this signature will be searched for
     * @return a list of pairs of start and end positions of the signature
     */
    QList< QPair<int,int> > signaturePositions( const KPIMIdentities::Signature &sig ) const;

    void slotAddAutoCorrect(const QString&, const QString&);
    // Data members
    QString extEditorPath;
    KMeditor *q;
    bool useExtEditor;
    bool forcePlainTextMarkup;
    QString quotePrefix;

    KProcess *mExtEditorProcess;
    KTemporaryFile *mExtEditorTempFile;
    PimCommon::AutoCorrection *mAutoCorrection;
private:
    void canStartProcess(const QString &commandLine);
};

}

using namespace MessageComposer;

void KMeditorPrivate::slotAddAutoCorrect(const QString&currentWord, const QString&replaceWord)
{
    if(mAutoCorrection) {
        mAutoCorrection->addAutoCorrect(currentWord,replaceWord);
    }
}

void KMeditorPrivate::startExternalEditor()
{
    const QString commandLine = extEditorPath.trimmed();
    if ( commandLine.isEmpty() ) {
        q->setUseExternalEditor( false );
        //KF5 add MessageBox
        //TODO: show messagebox
        return;
    }

    mExtEditorTempFile = new KTemporaryFile();
    if ( !mExtEditorTempFile->open() ) {
        delete mExtEditorTempFile;
        mExtEditorTempFile = 0;
        q->setUseExternalEditor( false );
        return;
    }

    mExtEditorTempFile->write( q->textOrHtml().toUtf8() );
    mExtEditorTempFile->close();

    mExtEditorProcess = new KProcess();
    // construct command line...
    QHash<QChar,QString> map;
    map.insert(QLatin1Char('l'), QString::number(q->textCursor().blockNumber() + 1));
    map.insert(QLatin1Char('w'), QString::number((qulonglong)q->winId()));
    map.insert(QLatin1Char('f'), mExtEditorTempFile->fileName());
    const QString cmd = KMacroExpander::expandMacrosShellQuote(commandLine, map);
    const QStringList arg = KShell::splitArgs(cmd);
    bool filenameAdded = false;
    if (commandLine.contains(QLatin1String("%f"))) {
        filenameAdded = true;
    }
    QStringList command;
    if ( !arg.isEmpty() )
        command<<arg;
    if (command.isEmpty()) {
        canStartProcess(commandLine);
        return;
    }
    ( *mExtEditorProcess ) << command;
    if ( !filenameAdded ) { // no %f in the editor command
        ( *mExtEditorProcess ) << mExtEditorTempFile->fileName();
    }

    QObject::connect( mExtEditorProcess, SIGNAL(finished(int,QProcess::ExitStatus)),
                      q, SLOT(slotEditorFinished(int,QProcess::ExitStatus)) );
    mExtEditorProcess->start();
    if ( !mExtEditorProcess->waitForStarted() ) {
        canStartProcess(commandLine);
    } else {
        emit q->externalEditorStarted();
    }
}

void KMeditorPrivate::canStartProcess(const QString &commandLine)
{
    KMessageBox::error(q->topLevelWidget(),i18n("External editor cannot be started. Please verify command \"%1\"",commandLine));
    q->killExternalEditor();
    q->setUseExternalEditor( false );
}

void KMeditorPrivate::slotEditorFinished( int codeError, QProcess::ExitStatus exitStatus )
{
    if ( exitStatus == QProcess::NormalExit ) {
        // the external editor could have renamed the original file and recreated a new file
        // with the given filename, so we need to reopen the file after the editor exited
        QFile localFile(mExtEditorTempFile->fileName());
        if ( localFile.open(QIODevice::ReadOnly | QIODevice::Text) ) {
            QByteArray f = localFile.readAll();
            q->setTextOrHtml( QString::fromUtf8( f.data(), f.size() ) );
            q->document()->setModified( true );
            localFile.close();
        }
        if (codeError > 0) {
            KMessageBox::error(q->topLevelWidget(), i18n("Error was found when we started external editor."), i18n("External Editor Closed"));
            q->setUseExternalEditor( false );
        }
        emit q->externalEditorClosed();
    }

    q->killExternalEditor();   // cleanup...

}

void KMeditorPrivate::ensureCursorVisibleDelayed()
{
    static_cast<KPIMTextEdit::TextEdit*>( q )->ensureCursorVisible();
}

void KMeditor::startExternalEditor()
{
    if ( d->useExtEditor && !d->mExtEditorProcess ) {
        d->startExternalEditor();
    }
}

static bool isSpecial( const QTextCharFormat &charFormat )
{
    return charFormat.isFrameFormat() || charFormat.isImageFormat() ||
            charFormat.isListFormat() || charFormat.isTableFormat() || charFormat.isTableCellFormat();
}

void KMeditor::keyPressEvent ( QKeyEvent *e )
{
    if ( d->useExtEditor &&
         ( e->key() != Qt::Key_Shift ) &&
         ( e->key() != Qt::Key_Control ) &&
         ( e->key() != Qt::Key_Meta ) &&
         ( e->key() != Qt::Key_CapsLock ) &&
         ( e->key() != Qt::Key_NumLock ) &&
         ( e->key() != Qt::Key_ScrollLock ) &&
         ( e->key() != Qt::Key_Alt ) &&
         ( e->key() != Qt::Key_AltGr ) ) {
        if ( !d->mExtEditorProcess ) {
            d->startExternalEditor();
        }
        return;
    }

    if ( e->key() == Qt::Key_Up && e->modifiers() != Qt::ShiftModifier &&
         textCursor().block().position() == 0 &&
         textCursor().block().layout()->lineForTextPosition( textCursor().position() ).lineNumber() == 0 ) {
        textCursor().clearSelection();
        emit focusUp();
    } else if ( e->key() == Qt::Key_Backtab && e->modifiers() == Qt::ShiftModifier ) {
        textCursor().clearSelection();
        emit focusUp();
    } else {
        if(d->mAutoCorrection && d->mAutoCorrection->isEnabledAutoCorrection()) {
            if((e->key() == Qt::Key_Space) || (e->key() == Qt::Key_Enter) || (e->key() == Qt::Key_Return)) {
                if(!isLineQuoted(textCursor().block().text()) && !textCursor().hasSelection()) {
                    const QTextCharFormat initialTextFormat = textCursor().charFormat();
                    const bool richText = (textMode() == KRichTextEdit::Rich);
                    int position = textCursor().position();
                    const bool addSpace = d->mAutoCorrection->autocorrect(richText, *document(), position);
                    QTextCursor cur = textCursor();
                    cur.setPosition(position);
                    if (overwriteMode() && e->key() == Qt::Key_Space) {
                        if (addSpace) {
                            const QChar insertChar = QLatin1Char(' ');
                            if (!cur.atBlockEnd()) {
                                cur.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 1);
                            }
                            if (richText && !isSpecial(initialTextFormat))
                                cur.insertText(insertChar, initialTextFormat);
                            else
                                cur.insertText(insertChar);
                            setTextCursor(cur);
                        }
                    } else {
                        const bool spacePressed = (e->key() == Qt::Key_Space);
                        const QChar insertChar = spacePressed ? QLatin1Char(' ') : QLatin1Char('\n');
                        if (richText && !isSpecial(initialTextFormat)) {
                            if ((spacePressed && addSpace) || !spacePressed) {
                                cur.insertText(insertChar, initialTextFormat);
                            }
                        } else {
                            if ((spacePressed && addSpace) || !spacePressed) {
                                cur.insertText(insertChar);
                            }
                        }
                        setTextCursor(cur);
                    }
                    return;
                }
            }
        }
        TextEdit::keyPressEvent( e );
    }
}

KMeditor::KMeditor( const QString &text, QWidget *parent )
    : TextEdit( text, parent ), d( new KMeditorPrivate( this ) )
{
    d->init();
}

KMeditor::KMeditor( QWidget *parent )
    : TextEdit( parent ), d( new KMeditorPrivate( this ) )
{
    d->init();
}

KMeditor::KMeditor( QWidget *parent, const QString & configFile )
    : TextEdit( parent, configFile ), d( new KMeditorPrivate( this ) )
{
    d->init();
}

KMeditor::~KMeditor()
{
    delete d;
}

void KMeditorPrivate::init()
{
    q->showAutoCorrectButton(true);
    QShortcut * insertMode = new QShortcut( QKeySequence( Qt::Key_Insert ), q );
    q->connect( insertMode, SIGNAL(activated()),
                q, SLOT(slotChangeInsertMode()) );
    q->connect(q,SIGNAL(spellCheckerAutoCorrect(QString,QString)),q, SLOT(slotAddAutoCorrect(QString,QString)));
}

void KMeditor::slotChangeInsertMode()
{
    setOverwriteMode( !overwriteMode() );
    emit insertModeChanged();
}

void KMeditor::setUseExternalEditor( bool use )
{
    d->useExtEditor = use;
}

void KMeditor::setExternalEditorPath( const QString &path )
{
    d->extEditorPath = path;
}

void KMeditor::setFontForWholeText( const QFont &font )
{
    QTextCharFormat fmt;
    fmt.setFont( font );
    QTextCursor cursor( document() );
    cursor.movePosition( QTextCursor::End, QTextCursor::KeepAnchor );
    cursor.mergeCharFormat( fmt );
    document()->setDefaultFont( font );
}

KUrl KMeditor::insertFile()
{
    QPointer<KEncodingFileDialog> fdlg =
            new KEncodingFileDialog( QString(), QString(),  QString(), QString(),
                                     KFileDialog::Opening, this );
    fdlg->okButton()->setText( i18nc( "@action:button", "&Insert" ) );
    fdlg->setCaption( i18nc( "@title:window", "Insert File" ) );

    KUrl url;
    if ( fdlg->exec() && fdlg ) {
        url = fdlg->selectedUrl();
        url.setFileEncoding( MessageViewer::NodeHelper::fixEncoding( fdlg->selectedEncoding() ) );
    }
    delete fdlg;
    return url;
}

void KMeditor::enableWordWrap( int wrapColumn )
{
    setWordWrapMode( QTextOption::WordWrap );
    setLineWrapMode( QTextEdit::FixedColumnWidth );
    setLineWrapColumnOrWidth( wrapColumn );
}

void KMeditor::disableWordWrap()
{
    setLineWrapMode( QTextEdit::WidgetWidth );
}

void KMeditor::slotPasteAsQuotation()
{
#ifndef QT_NO_CLIPBOARD
    if ( hasFocus() ) {
        const QString s = QApplication::clipboard()->text();
        if ( !s.isEmpty() ) {
            insertPlainText( d->addQuotesToText( s ) );
        }
    }
#endif
}

void KMeditor::slotPasteWithoutFormatting()
{
#ifndef QT_NO_CLIPBOARD
    if ( hasFocus() ) {
        const QString s = QApplication::clipboard()->text();
        if ( !s.isEmpty() ) {
            insertPlainText( s );
        }
    }
#endif
}

void KMeditor::slotRemoveQuotes()
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
    if ( !cursor.hasSelection() ) {
        cursor.select( QTextCursor::Document );
    }

    QTextBlock block = document()->findBlock( cursor.selectionStart() );
    int selectionEnd = cursor.selectionEnd();
    while ( block.isValid() && block.position() <= selectionEnd ) {
        cursor.setPosition( block.position() );
        if ( isLineQuoted( block.text() ) ) {
            int length = quoteLength( block.text() );
            cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor, length );
            cursor.removeSelectedText();
            selectionEnd -= length;
        }
        block = block.next();
    }
    cursor.clearSelection();
    cursor.endEditBlock();
}

void KMeditor::slotAddQuotes()
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
    QString selectedText;
    if ( !cursor.hasSelection() ) {
        cursor.select( QTextCursor::Document );
        selectedText = cursor.selectedText();
        cursor.removeSelectedText();
    } else {
        selectedText = cursor.selectedText();
    }
    insertPlainText( d->addQuotesToText( selectedText ) );
    cursor.endEditBlock();
}

QString KMeditorPrivate::addQuotesToText( const QString &inputText )
{
    QString answer = inputText;
    const QString indentStr = q->defaultQuoteSign();
    answer.replace( QLatin1Char( '\n' ), QLatin1Char( '\n' ) + indentStr );
    //cursor.selectText() as QChar::ParagraphSeparator as paragraph separator.
    answer.replace( QChar::ParagraphSeparator, QLatin1Char( '\n' ) + indentStr );
    answer.prepend( indentStr );
    answer += QLatin1Char( '\n' );
    return q->smartQuote( answer );
}


const QString KMeditor::defaultQuoteSign() const
{
    if ( !d->quotePrefix.simplified().isEmpty() )
        return d->quotePrefix;
    else
        return KPIMTextEdit::TextEdit::defaultQuoteSign();
}

int KMeditor::quoteLength( const QString& line ) const
{
    if ( !d->quotePrefix.simplified().isEmpty() ) {
        if ( line.startsWith( d->quotePrefix ) )
            return d->quotePrefix.length();
        else
            return 0;
    }
    else
        return KPIMTextEdit::TextEdit::quoteLength( line );
}

void KMeditor::setQuotePrefixName( const QString &quotePrefix )
{
    d->quotePrefix = quotePrefix;
}

QString KMeditor::quotePrefixName() const
{
    if ( !d->quotePrefix.simplified().isEmpty() )
        return d->quotePrefix;
    else
        return QLatin1String( ">" );
}

QString KMeditor::smartQuote( const QString &msg )
{
    return msg;
}

bool KMeditor::checkExternalEditorFinished()
{
    if ( !d->mExtEditorProcess ) {
        return true;
    }

    int ret = KMessageBox::warningYesNoCancel(
                topLevelWidget(),
                i18nc( "@info",
                       "The external editor is still running.<nl/>"
                       "Do you want to stop the editor or keep it running?<nl/>"
                       "<warning>Stopping the editor will cause all your "
                       "unsaved changes to be lost.</warning>" ),
                i18nc( "@title:window", "External Editor Running" ),
                KGuiItem( i18nc( "@action:button", "Stop Editor" ) ),
                KGuiItem( i18nc( "@action:button", "Keep Editor Running" ) ) );

    switch( ret ) {
    case KMessageBox::Yes:
        killExternalEditor();
        return true;
    case KMessageBox::No:
        return true;
    default:
        return false;
    }
}

void KMeditor::killExternalEditor()
{
    if ( d->mExtEditorProcess ) {
        d->mExtEditorProcess->deleteLater();
    }
    d->mExtEditorProcess = 0;
    delete d->mExtEditorTempFile;
    d->mExtEditorTempFile = 0;
}

void KMeditor::setCursorPositionFromStart( unsigned int pos )
{
    if ( pos > 0 ) {
        QTextCursor cursor = textCursor();
        //Fix html pos cursor
        cursor.setPosition( qMin( pos,(unsigned int)cursor.document()->characterCount ()-1) );
        setTextCursor( cursor );
        ensureCursorVisible();
    }
}

int KMeditor::linePosition() const
{
    const QTextCursor cursor = textCursor();
    const QTextDocument *doc = document();
    QTextBlock block = doc->begin();
    int lineCount = 0;

    // Simply using cursor.block.blockNumber() would not work since that does not
    // take word-wrapping into account, i.e. it is possible to have more than one
    // line in a block.
    //
    // What we have to do therefore is to iterate over the blocks and count the
    // lines in them. Once we have reached the block where the cursor is, we have
    // to iterate over each line in it, to find the exact line in the block where
    // the cursor is.
    while ( block.isValid() ) {
        const QTextLayout *layout = block.layout();

        // If the current block has the cursor in it, iterate over all its lines
        if ( block == cursor.block() ) {

            // Special case: Cursor at end of single non-wrapped line, exit early
            // in this case as the logic below can't handle it
            if ( block.lineCount() == layout->lineCount() ) {
                return lineCount;
            }

            const int cursorBasePosition = cursor.position() - block.position();
            const int numberOfLine( layout->lineCount() );
            for ( int i = 0; i < numberOfLine; ++i ) {
                QTextLine line = layout->lineAt( i );
                if ( cursorBasePosition >= line.textStart() &&
                     cursorBasePosition < line.textStart() + line.textLength() ) {
                    break;
                }
                lineCount++;
            }
            return lineCount;
        } else {
            // No, cursor is not in the current block
            lineCount += layout->lineCount();
        }

        block = block.next();
    }

    // Only gets here if the cursor block can't be found, shouldn't happen except
    // for an empty document maybe
    return lineCount;
}

int KMeditor::columnNumber() const
{
    const QTextCursor cursor = textCursor();
    return cursor.columnNumber();
}

void KMeditor::ensureCursorVisible()
{
    QCoreApplication::processEvents();

    // Hack: In KMail, the layout of the composer changes again after
    //       creating the editor (the toolbar/menubar creation is delayed), so
    //       the size of the editor changes as well, possibly hiding the cursor
    //       even though we called ensureCursorVisible() before the layout phase.
    //
    //       Delay the actual call to ensureCursorVisible() a bit to work around
    //       the problem.
    QTimer::singleShot( 500, this, SLOT(ensureCursorVisibleDelayed()) );
}

void KMeditorPrivate::cleanWhitespaceHelper( const QRegExp &regExp,
                                             const QString &newText,
                                             const KPIMIdentities::Signature &sig )
{
    int currentSearchPosition = 0;

    forever {

        // Find the text
        QString text = q->document()->toPlainText();
        int currentMatch = regExp.indexIn( text, currentSearchPosition );
        currentSearchPosition = currentMatch;
        if ( currentMatch == -1 ) {
            break;
        }

        // Select the text
        QTextCursor cursor( q->document() );
        cursor.setPosition( currentMatch );
        cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
                             regExp.matchedLength() );

        // Skip quoted text
        if ( q->isLineQuoted( cursor.block().text() ) ) {
            currentSearchPosition += regExp.matchedLength();
            continue;
        }

        // Skip text inside signatures
        bool insideSignature = false;
        QList< QPair<int,int> > sigPositions = signaturePositions( sig );
        QPair<int,int> position;
        foreach ( position, sigPositions ) { //krazy:exclude=foreach
            if ( cursor.position() >= position.first &&
                 cursor.position() <= position.second ) {
                insideSignature = true;
            }
        }
        if ( insideSignature ) {
            currentSearchPosition += regExp.matchedLength();
            continue;
        }

        // Replace the text
        cursor.removeSelectedText();
        cursor.insertText( newText );
        currentSearchPosition += newText.length();
    }
}

void KMeditor::cleanWhitespace( const KPIMIdentities::Signature &sig )
{
    QTextCursor cursor( document() );
    cursor.beginEditBlock();

    // Squeeze tabs and spaces
    d->cleanWhitespaceHelper( QRegExp( QLatin1String( "[\t ]+" ) ),
                              QString( QLatin1Char( ' ' ) ), sig );

    // Remove trailing whitespace
    d->cleanWhitespaceHelper( QRegExp( QLatin1String( "[\t ][\n]" ) ),
                              QString( QLatin1Char( '\n' ) ), sig );

    // Single space lines
    d->cleanWhitespaceHelper( QRegExp( QLatin1String( "[\n]{3,}" ) ),
                              QLatin1String( "\n\n" ), sig );

    if ( !textCursor().hasSelection() ) {
        textCursor().clearSelection();
    }

    cursor.endEditBlock();
}

QList< QPair<int,int> >
KMeditorPrivate::signaturePositions( const KPIMIdentities::Signature &sig ) const
{
    QList< QPair<int,int> > signaturePositions;
    if ( !sig.rawText().isEmpty() ) {

        QString sigText = sig.toPlainText();

        int currentSearchPosition = 0;
        forever {

            // Find the next occurrence of the signature text
            const QString text = q->document()->toPlainText();
            const int currentMatch = text.indexOf( sigText, currentSearchPosition );
            currentSearchPosition = currentMatch + sigText.length();
            if ( currentMatch == -1 ) {
                break;
            }

            signaturePositions.append( QPair<int,int>( currentMatch,
                                                       currentMatch + sigText.length() ) );
        }
    }
    return signaturePositions;
}

bool KMeditor::replaceSignature( const KPIMIdentities::Signature &oldSig,
                                 const KPIMIdentities::Signature &newSig )
{
    bool found = false;
    QString oldSigText = oldSig.toPlainText();
    if ( oldSigText.isEmpty() )
        return false;

    QTextCursor cursor( document() );
    cursor.beginEditBlock();
    int currentSearchPosition = 0;
    forever {

        // Find the next occurrence of the signature text
        const QString text = document()->toPlainText();
        int currentMatch = text.indexOf( oldSigText, currentSearchPosition );
        currentSearchPosition = currentMatch;
        if ( currentMatch == -1 ) {
            break;
        }

        // Select the signature
        QTextCursor cursor( document() );
        cursor.setPosition( currentMatch );

        // If the new signature is completely empty, we also want to remove the
        // signature separator, so include it in the selection
        int additionalMove = 0;
        if ( newSig.rawText().isEmpty() &&
             text.mid( currentMatch - 4, 4 ) == QLatin1String( "-- \n" ) ) {
            cursor.movePosition( QTextCursor::PreviousCharacter,
                                 QTextCursor::MoveAnchor, 5 );
            additionalMove = 5;
        }
        cursor.movePosition( QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
                             oldSigText.length() + additionalMove );

        // Skip quoted signatures
        if ( isLineQuoted( cursor.block().text() ) ) {
            currentSearchPosition += oldSig.toPlainText().length();
            continue;
        }

        // Remove the old and insert the new signature
        cursor.removeSelectedText();
        setTextCursor( cursor );
        newSig.insertIntoTextEdit( KPIMIdentities::Signature::AtCursor,
                                   KPIMIdentities::Signature::AddNothing, this );
        found = true;

        currentSearchPosition += newSig.toPlainText().length();
    }

    cursor.endEditBlock();
    return found;
}

void KMeditor::fillComposerTextPart ( MessageComposer::TextPart* textPart )
{
    if( isFormattingUsed() && MessageComposer::MessageComposerSettings::self()->improvePlainTextOfHtmlMessage() ) {
        Grantlee::PlainTextMarkupBuilder *pb = new Grantlee::PlainTextMarkupBuilder();

        Grantlee::MarkupDirector *pmd = new Grantlee::MarkupDirector( pb );
        pmd->processDocument( document() );
        const QString plainText = pb->getResult();
        textPart->setCleanPlainText( toCleanPlainText(plainText) );
        QTextDocument *doc = new QTextDocument(plainText);
        doc->adjustSize();

        textPart->setWrappedPlainText(toWrappedPlainText(doc));
        delete doc;
        delete pmd;
        delete pb;
    } else {
        textPart->setCleanPlainText( toCleanPlainText() );
        textPart->setWrappedPlainText( toWrappedPlainText() );
    }
    textPart->setWordWrappingEnabled( lineWrapMode() == QTextEdit::FixedColumnWidth );
    if( isFormattingUsed() ) {
        QString cleanHtml = toCleanHtml();
        fixHtmlFontSize(cleanHtml);
        textPart->setCleanHtml( cleanHtml );
        textPart->setEmbeddedImages( embeddedImages() );
    }
}


void KMeditor::fixHtmlFontSize(QString &cleanHtml)
{
    static const QString FONTSTYLEREGEX = QLatin1String("<span style=\".*font-size:(.*)pt;.*</span>" );
    QRegExp styleRegex( FONTSTYLEREGEX );
    styleRegex.setMinimal( true );

    int offset = styleRegex.indexIn( cleanHtml, 0 );
    while (offset != -1) {
      // replace all the matching text with the new line text
        bool ok = false;
        const QString fontSizeStr = styleRegex.cap(1);
        const int ptValue = fontSizeStr.toInt(&ok);
        if (ok) {
            double emValue = (double)ptValue/12;
            const QString emValueStr = QString::number(emValue, 'g', 2);
            cleanHtml.replace(styleRegex.pos(1), QString(fontSizeStr + QLatin1String("px")).length(), emValueStr + QLatin1String("em"));
        }
        // advance the search offset to just beyond the last replace
        offset += styleRegex.matchedLength();
        // find the next occurance
        offset = styleRegex.indexIn( cleanHtml, offset );
    }
}

PimCommon::AutoCorrection* KMeditor::autocorrection() const
{
    return d->mAutoCorrection;
}

void KMeditor::setAutocorrection(PimCommon::AutoCorrection* autocorrect)
{
    d->mAutoCorrection = autocorrect;
}

void KMeditor::setAutocorrectionLanguage(const QString& lang)
{
    d->mAutoCorrection->setLanguage(lang);
}

void KMeditor::forcePlainTextMarkup(bool force)
{
    d->forcePlainTextMarkup = force;
}

void KMeditor::insertPlainTextImplementation()
{
    if( d->forcePlainTextMarkup ) {
        Grantlee::PlainTextMarkupBuilder *pb = new Grantlee::PlainTextMarkupBuilder();

        Grantlee::MarkupDirector *pmd = new Grantlee::MarkupDirector( pb );
        pmd->processDocument( document() );
        const QString plainText = pb->getResult();
        document()->setPlainText(plainText);
        delete pmd;
        delete pb;
    } else {
        document()->setPlainText(document()->toPlainText());
    }
}

void KMeditor::insertLink(const QString &url)
{
    if (url.isEmpty())
        return;
    if (textMode() == KRichTextEdit::Rich) {
        QTextCursor cursor = textCursor();
        cursor.beginEditBlock();

        QTextCharFormat format = cursor.charFormat();
        // Save original format to create an extra space with the existing char
        // format for the block
        const QTextCharFormat originalFormat = format;
        // Add link details
        format.setAnchor(true);
        format.setAnchorHref(url);
        // Workaround for QTBUG-1814:
        // Link formatting does not get applied immediately when setAnchor(true)
        // is called.  So the formatting needs to be applied manually.
        format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
        format.setUnderlineColor(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color());
        format.setForeground(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color());
        // Insert link text specified in dialog, otherwise write out url.
        cursor.insertText(url, format);

        cursor.setPosition(cursor.selectionEnd());
        cursor.setCharFormat(originalFormat);
        cursor.insertText(QLatin1String(" \n"));
        cursor.endEditBlock();
    } else {
        textCursor().insertText(url + QLatin1Char('\n'));
    }
}

void KMeditor::insertShareLink(const QString &url)
{
    if (url.isEmpty())
        return;
    const QString msg = i18n("I've linked 1 file to this email:");
    if (textMode() == KRichTextEdit::Rich) {
        QTextCursor cursor = textCursor();

        cursor.beginEditBlock();
        cursor.insertText(QLatin1Char('\n') + msg + QLatin1Char('\n'));

        QTextCharFormat format = cursor.charFormat();
        // Save original format to create an extra space with the existing char
        // format for the block
        const QTextCharFormat originalFormat = format;
        // Add link details
        format.setAnchor(true);
        format.setAnchorHref(url);
        // Workaround for QTBUG-1814:
        // Link formatting does not get applied immediately when setAnchor(true)
        // is called.  So the formatting needs to be applied manually.
        format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
        format.setUnderlineColor(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color());
        format.setForeground(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color());
        // Insert link text specified in dialog, otherwise write out url.
        cursor.insertText(url, format);

        cursor.setPosition(cursor.selectionEnd());
        cursor.setCharFormat(originalFormat);
        cursor.insertText(QLatin1String(" \n"));
        cursor.endEditBlock();
    } else {
        textCursor().insertText(QLatin1Char('\n') + msg + QLatin1Char('\n') + url + QLatin1Char('\n'));
    }
}

#include "moc_kmeditor.cpp"
