/*******************************************************************
 KNotes -- Notes for the KDE project

 Copyright (c) 1997-2013, The KNotes Developers

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program 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 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*******************************************************************/

#include "knoteedit.h"
#include "notes/knote.h"
#include "noteshared/editor/noteeditorutils.h"
#include "pimcommon/util/editorutil.h"
#include "knotesglobalconfig.h"

#include <kaction.h>
#include <kactioncollection.h>
#include <kcolordialog.h>
#include <kdebug.h>
#include <kfontaction.h>
#include <kfontsizeaction.h>
#include <kicon.h>
#include <klocale.h>
#include <kmenu.h>
#include <kstandardaction.h>
#include <ktoggleaction.h>
#include <kurl.h>

#include <QFont>
#include <QPixmap>
#include <QKeyEvent>

static const short SEP = 5;
static const short ICON_SIZE = 10;


KNoteEdit::KNoteEdit( const QString &configFile, KActionCollection *actions, QWidget *parent )
    : PimCommon::CustomTextEdit(configFile, parent ),
      m_note( 0 ),
      m_actions( actions )
{
    setAcceptDrops( true );
    setWordWrapMode( QTextOption::WordWrap );
    setLineWrapMode( WidgetWidth );
    if ( acceptRichText() ) {
        setAutoFormatting( AutoAll );
    } else {
        setAutoFormatting( AutoNone );
    }

    // create the actions modifying the text format
    m_textBold  = new KToggleAction( KIcon( QLatin1String("format-text-bold") ), i18n( "Bold" ),
                                     this );
    actions->addAction( QLatin1String("format_bold"), m_textBold );
    m_textBold->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_B ) );
    m_textItalic  = new KToggleAction( KIcon( QLatin1String("format-text-italic") ),
                                       i18n( "Italic" ), this );
    actions->addAction( QLatin1String("format_italic"), m_textItalic );
    m_textItalic->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_I ) );
    m_textUnderline  = new KToggleAction( KIcon( QLatin1String("format-text-underline") ),
                                          i18n( "Underline" ), this );
    actions->addAction( QLatin1String("format_underline"), m_textUnderline );
    m_textUnderline->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_U ) );
    m_textStrikeOut  = new KToggleAction( KIcon( QLatin1String("format-text-strikethrough") ),
                                          i18n( "Strike Out" ), this );
    actions->addAction( QLatin1String("format_strikeout"), m_textStrikeOut );
    m_textStrikeOut->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_S ) );

    connect( m_textBold, SIGNAL(toggled(bool)), SLOT(textBold(bool)) );
    connect( m_textItalic, SIGNAL(toggled(bool)),
             SLOT(setFontItalic(bool)) );
    connect( m_textUnderline, SIGNAL(toggled(bool)),
             SLOT(setFontUnderline(bool)) );
    connect( m_textStrikeOut, SIGNAL(toggled(bool)),
             SLOT(textStrikeOut(bool)) );

    m_textAlignLeft = new KToggleAction( KIcon( QLatin1String("format-justify-left") ),
                                         i18n( "Align Left" ), this );
    actions->addAction( QLatin1String("format_alignleft"), m_textAlignLeft );
    connect( m_textAlignLeft, SIGNAL(triggered(bool)),
             SLOT(textAlignLeft()) );
    m_textAlignLeft->setShortcut( QKeySequence( Qt::ALT + Qt::Key_L ) );
    m_textAlignLeft->setChecked( true ); // just a dummy, will be updated later
    m_textAlignCenter  = new KToggleAction( KIcon( QLatin1String("format-justify-center") ),
                                            i18n( "Align Center" ), this );
    actions->addAction( QLatin1String("format_aligncenter"), m_textAlignCenter );
    connect( m_textAlignCenter, SIGNAL(triggered(bool)),
             SLOT(textAlignCenter()) );
    m_textAlignCenter->setShortcut( QKeySequence( Qt::ALT + Qt::Key_C ) );
    m_textAlignRight = new KToggleAction( KIcon( QLatin1String("format-justify-right") ),
                                          i18n( "Align Right" ), this );
    actions->addAction( QLatin1String("format_alignright"), m_textAlignRight );
    connect( m_textAlignRight, SIGNAL(triggered(bool)),
             SLOT(textAlignRight()) );
    m_textAlignRight->setShortcut( QKeySequence( Qt::ALT + Qt::Key_R ) );
    m_textAlignBlock = new KToggleAction( KIcon( QLatin1String("format-justify-fill") ),
                                          i18n( "Align Block" ), this );
    actions->addAction( QLatin1String("format_alignblock"), m_textAlignBlock );
    connect( m_textAlignBlock, SIGNAL(triggered(bool)),
             SLOT(textAlignBlock()) );
    m_textAlignBlock->setShortcut( QKeySequence( Qt::ALT + Qt::Key_B ) );

    QActionGroup *group = new QActionGroup( this );
    group->addAction( m_textAlignLeft );
    group->addAction( m_textAlignCenter );
    group->addAction( m_textAlignRight );
    group->addAction( m_textAlignBlock );

    m_textList  = new KToggleAction( KIcon( QLatin1String("format-list-ordered") ), i18n( "List" ), this );
    actions->addAction( QLatin1String("format_list"), m_textList );
    connect( m_textList, SIGNAL(triggered(bool)), SLOT(textList()) );

    m_textSuper  = new KToggleAction( KIcon( QLatin1String("format-text-superscript") ),
                                      i18n( "Superscript" ), this );
    actions->addAction( QLatin1String("format_super"), m_textSuper );
    connect( m_textSuper, SIGNAL(triggered(bool)),
             SLOT(textSuperScript()) );
    m_textSub  = new KToggleAction( KIcon( QLatin1String("format-text-subscript") ), i18n( "Subscript" ),
                                    this );
    actions->addAction( QLatin1String("format_sub"), m_textSub );
    connect( m_textSub, SIGNAL(triggered(bool)), SLOT(textSubScript()) );


    m_textIncreaseIndent = new KAction( KIcon( QLatin1String("format-indent-more") ),
                                        i18n( "Increase Indent" ), this );
    actions->addAction( QLatin1String("format_increaseindent"), m_textIncreaseIndent );
    m_textIncreaseIndent->setShortcut( QKeySequence( Qt::CTRL + Qt::ALT +
                                                     Qt::Key_I ) );
    connect( m_textIncreaseIndent, SIGNAL(triggered(bool)),
             SLOT(textIncreaseIndent()) );

    m_textDecreaseIndent = new KAction(  KIcon( QLatin1String("format-indent-less") ),
                                         i18n( "Decrease Indent" ), this );
    actions->addAction( QLatin1String("format_decreaseindent"), m_textDecreaseIndent );
    m_textDecreaseIndent->setShortcut( QKeySequence( Qt::CTRL + Qt::ALT +
                                                     Qt::Key_D ) );
    connect( m_textDecreaseIndent, SIGNAL(triggered(bool)), SLOT(
                 textDecreaseIndent() ) );

    group = new QActionGroup( this );
    group->addAction( m_textIncreaseIndent );
    group->addAction( m_textDecreaseIndent );

    QPixmap pix( ICON_SIZE, ICON_SIZE );
    pix.fill( Qt::black ); // just a dummy, gets updated before widget is shown
    m_textColor  = new KAction( i18n( "Text Color..." ), this );
    actions->addAction( QLatin1String("format_color"), m_textColor );
    m_textColor->setIcon( pix );
    connect( m_textColor, SIGNAL(triggered(bool)), SLOT(slotTextColor()) );

    KAction *act = new KAction(KIcon( QLatin1String("format-fill-color") ), i18n( "Text Background Color..." ), this );
    actions->addAction( QLatin1String("text_background_color"), act );
    connect( act, SIGNAL(triggered(bool)), SLOT(slotTextBackgroundColor()) );

    m_textFont  = new KFontAction( i18n( "Text Font" ), this );
    actions->addAction( QLatin1String("format_font"), m_textFont );
    connect( m_textFont, SIGNAL(triggered(QString)),
             this, SLOT(setFontFamily(QString)) );

    m_textSize  = new KFontSizeAction( i18n( "Text Size" ), this );
    actions->addAction( QLatin1String("format_size"), m_textSize );
    connect( m_textSize, SIGNAL(fontSizeChanged(int)),
             this, SLOT(setTextFontSize(int)) );

    KAction *action = new KAction( i18n("Uppercase"), this );
    actions->addAction( QLatin1String("change_to_uppercase"), action );
    connect( action, SIGNAL(triggered(bool)), this, SLOT(slotUpperCase()) );

    action = new KAction( i18n("Sentence case"), this );
    actions->addAction( QLatin1String("change_to_sentencecase"), action );
    connect( action, SIGNAL(triggered(bool)), this, SLOT(slotSentenceCase()) );

    action = new KAction( i18n("Lowercase"), this );
    actions->addAction( QLatin1String("change_to_lowercase"), action );
    connect( action, SIGNAL(triggered(bool)), this, SLOT(slotLowerCase()) );

    action  = new KAction( KIcon( QLatin1String("knotes_date") ), i18n( "Insert Date" ), this );
    actions->addAction( QLatin1String("insert_date"), action );
    connect( action, SIGNAL(triggered(bool)), SLOT(slotInsertDate()) );

    action = new KAction( KIcon( QLatin1String("checkmark") ), i18n( "Insert Checkmark" ), this );
    actions->addAction( QLatin1String("insert_checkmark"), action );
    connect( action, SIGNAL(triggered(bool)), SLOT(slotInsertCheckMark()) );

    // QTextEdit connections
    connect( this, SIGNAL(currentCharFormatChanged(QTextCharFormat)),
             SLOT(slotCurrentCharFormatChanged(QTextCharFormat)) );
    connect( this, SIGNAL(cursorPositionChanged()),
             SLOT(slotCursorPositionChanged()) );
    slotCurrentCharFormatChanged( currentCharFormat() );
    slotCursorPositionChanged();
}

KNoteEdit::~KNoteEdit()
{
}

void KNoteEdit::setColor(const QColor &fg, const QColor &bg)
{
    mDefaultBackgroundColor = bg;
    mDefaultForegroundColor = fg;

    QPalette p = palette();

    // better: from light(150) to light(100) to light(75)
    // QLinearGradient g( width()/2, 0, width()/2, height() );
    // g.setColorAt( 0, bg );
    // g.setColorAt( 1, bg.dark(150) );

    p.setColor( QPalette::Window,     bg );
    // p.setBrush( QPalette::Window,     g );
    p.setColor( QPalette::Base,       bg );
    // p.setBrush( QPalette::Base,       g );

    p.setColor( QPalette::WindowText, fg );
    p.setColor( QPalette::Text,       fg );

    p.setColor( QPalette::Button,     bg.dark( 116 ) );
    p.setColor( QPalette::ButtonText, fg );

    //p.setColor( QPalette::Highlight,  bg );
    //p.setColor( QPalette::HighlightedText, fg );

    // order: Light, Midlight, Button, Mid, Dark, Shadow

    // the shadow
    p.setColor( QPalette::Light, bg.light( 180 ) );
    p.setColor( QPalette::Midlight, bg.light( 150 ) );
    p.setColor( QPalette::Mid, bg.light( 150 ) );
    p.setColor( QPalette::Dark, bg.dark( 108 ) );
    p.setColor( QPalette::Shadow, bg.dark( 116 ) );

    setPalette( p );

    setTextColor( fg );
}

void KNoteEdit::setNote( KNote *_note )
{
    m_note = _note;
}

void KNoteEdit::slotSentenceCase()
{
    QTextCursor cursor = textCursor();
    PimCommon::EditorUtil::sentenceCase(cursor);
}

void KNoteEdit::slotUpperCase()
{
    QTextCursor cursor = textCursor();
    PimCommon::EditorUtil::upperCase(cursor);
}

void KNoteEdit::slotLowerCase()
{
    QTextCursor cursor = textCursor();
    PimCommon::EditorUtil::lowerCase(cursor);
}

void KNoteEdit::mousePopupMenuImplementation(const QPoint& pos)
{
    QMenu *popup = mousePopupMenu();
    if ( popup ) {
        QTextCursor cursor = textCursor();
        if (!isReadOnly() ) {
            if (cursor.hasSelection()) {
                popup->addSeparator();
                QMenu *changeCaseMenu = new QMenu(i18n("Change case..."), popup);
                QAction * act = m_actions->action(QLatin1String("change_to_sentencecase"));
                changeCaseMenu->addAction(act);
                act = m_actions->action(QLatin1String("change_to_lowercase"));
                changeCaseMenu->addAction(act);
                act = m_actions->action(QLatin1String("change_to_uppercase"));
                changeCaseMenu->addAction(act);
                popup->addMenu(changeCaseMenu);
            }
            popup->addSeparator();
            QAction *act = m_actions->action(QLatin1String("insert_date"));
            popup->addAction(act);
            popup->addSeparator();
            act = m_actions->action(QLatin1String("insert_checkmark"));
            popup->addAction(act);
        }
        aboutToShowContextMenu(popup);
        popup->exec( pos );
        delete popup;
    }
}

void KNoteEdit::setText( const QString& text )
{
    if ( acceptRichText() && Qt::mightBeRichText( text ) ) {
        setHtml( text );
    } else {
        setPlainText( text );
    }
}

QString KNoteEdit::text() const
{
    if ( acceptRichText() ) {
        return toHtml();
    } else {
        return toPlainText();
    }
}

void KNoteEdit::setTextFont( const QFont &font )
{
    setCurrentFont( font );

    // make this font default so that if user deletes note content
    // font is remembered
    document()->setDefaultFont( font );
}

void KNoteEdit::setTextFontSize( int size )
{
    setFontPointSize( size );
}

void KNoteEdit::setTabStop( int tabs )
{
    QFontMetrics fm( font() );
    setTabStopWidth( fm.width( QLatin1Char('x') ) * tabs );
}

void KNoteEdit::setAutoIndentMode( bool newmode )
{
    m_autoIndentMode = newmode;
}


/** public slots **/

void KNoteEdit::setRichText( bool f )
{
    if ( f == acceptRichText() ) {
        return;
    }

    setAcceptRichText( f );

    if ( f ) {
        setAutoFormatting( AutoAll );
    } else {
        setAutoFormatting( AutoNone );
    }

    const QString t = toPlainText();
    if ( f ) {
        // if the note contains html source try to render it
        if ( Qt::mightBeRichText( t ) ) {
            setHtml( t );
        } else {
            setPlainText( t );
        }

        enableRichTextActions(true);
    } else {
        setPlainText( t );
        enableRichTextActions(false);
    }
}

void KNoteEdit::textBold( bool b )
{
    if (!acceptRichText())
        return;

    QTextCharFormat f;
    f.setFontWeight( b ? QFont::Bold : QFont::Normal );
    mergeCurrentCharFormat( f );
}

void KNoteEdit::textStrikeOut( bool s )
{
    if (!acceptRichText())
        return;

    QTextCharFormat f;
    f.setFontStrikeOut( s );
    mergeCurrentCharFormat( f );
}

void KNoteEdit::slotTextColor()
{
    if (!acceptRichText())
        return;

    if ( m_note )
        m_note->setBlockSave( true );
    QColor c = textColor();
    if ( KColorDialog::getColor( c, mDefaultForegroundColor, this ) ) {
        setTextColor( c.isValid() ? c : mDefaultForegroundColor);
    }
    if ( m_note )
        m_note->setBlockSave( false );
}

void KNoteEdit::slotTextBackgroundColor()
{
    if (!acceptRichText())
        return;

    if ( m_note )
        m_note->setBlockSave( true );
    QColor c = textBackgroundColor();
    if ( KColorDialog::getColor( c, mDefaultBackgroundColor, this ) ) {
        setTextBackgroundColor( c.isValid() ? c : mDefaultBackgroundColor );
    }
    if ( m_note )
        m_note->setBlockSave( false );
}

void KNoteEdit::textAlignLeft()
{
    if (!acceptRichText())
        return;
    setAlignment( Qt::AlignLeft );
    m_textAlignLeft->setChecked( true );
}

void KNoteEdit::textAlignCenter()
{
    if (!acceptRichText())
        return;
    setAlignment( Qt::AlignCenter );
    m_textAlignCenter->setChecked( true );
}

void KNoteEdit::textAlignRight()
{
    if (!acceptRichText())
        return;
    setAlignment( Qt::AlignRight );
    m_textAlignRight->setChecked( true );
}

void KNoteEdit::textAlignBlock()
{
    if (!acceptRichText())
        return;
    setAlignment( Qt::AlignJustify );
    m_textAlignBlock->setChecked( true );
}

void KNoteEdit::textList()
{
    if (!acceptRichText())
        return;
    QTextCursor c = textCursor();
    c.beginEditBlock();

    if ( m_textList->isChecked() ) {
        QTextListFormat lf;
        QTextBlockFormat bf = c.blockFormat();

        lf.setIndent( bf.indent() + 1 );
        bf.setIndent( 0 );

        lf.setStyle( QTextListFormat::ListDisc );

        c.setBlockFormat( bf );
        c.createList( lf );
    } else {
        QTextBlockFormat bf;
        bf.setObjectIndex( -1 );
        c.setBlockFormat( bf );

    }

    c.endEditBlock();
}

void KNoteEdit::textSuperScript()
{
    if (!acceptRichText())
        return;
    QTextCharFormat f;
    if ( m_textSuper->isChecked() ) {
        if ( m_textSub->isChecked() )
            m_textSub->setChecked( false );
        f.setVerticalAlignment( QTextCharFormat::AlignSuperScript );
    } else {
        f.setVerticalAlignment( QTextCharFormat::AlignNormal );
    }
    mergeCurrentCharFormat( f );
}

void KNoteEdit::textSubScript()
{
    if (!acceptRichText())
        return;
    QTextCharFormat f;
    if ( m_textSub->isChecked() ) {
        if ( m_textSuper->isChecked() )
            m_textSuper->setChecked( false );
        f.setVerticalAlignment( QTextCharFormat::AlignSubScript );
    } else {
        f.setVerticalAlignment( QTextCharFormat::AlignNormal );
    }
    mergeCurrentCharFormat( f );
}

void KNoteEdit::textIncreaseIndent()
{
    if (!acceptRichText())
        return;
    QTextBlockFormat f = textCursor().blockFormat();
    f.setIndent( f.indent() + 1 );
    textCursor().setBlockFormat( f );
}

void KNoteEdit::textDecreaseIndent()
{
    if (!acceptRichText())
        return;
    QTextBlockFormat f = textCursor().blockFormat();
    short int curIndent = f.indent();

    if ( curIndent > 0 ) {
        f.setIndent( curIndent - 1 );
    }
    textCursor().setBlockFormat( f );
}


/** protected methods **/

void KNoteEdit::keyPressEvent( QKeyEvent *e )
{
    KTextEdit::keyPressEvent( e );

    if ( m_autoIndentMode &&
         ( ( e->key() == Qt::Key_Return ) || ( e->key() == Qt::Key_Enter ) ) ) {
        autoIndent();
    }
}

void KNoteEdit::focusInEvent( QFocusEvent *e )
{
    KTextEdit::focusInEvent( e );

    setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
    setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
}

void KNoteEdit::focusOutEvent( QFocusEvent *e )
{
    setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
    setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );

    KTextEdit::focusOutEvent( e );
}

/** private slots **/

void KNoteEdit::slotCurrentCharFormatChanged( const QTextCharFormat &f )
{
    if (!acceptRichText())
        return;

    // font changes
    m_textFont->setFont( f.fontFamily() );
    m_textSize->setFontSize( (f.fontPointSize()>0 ) ?  ( int ) f.fontPointSize() :10);

    m_textBold->setChecked( f.font().bold() );
    m_textItalic->setChecked( f.fontItalic() );
    m_textUnderline->setChecked( f.fontUnderline() );
    m_textStrikeOut->setChecked( f.fontStrikeOut() );

    // color changes
    QPixmap pix( ICON_SIZE, ICON_SIZE );
    pix.fill( f.foreground().color() );
    m_textColor->QAction::setIcon( pix );

    // vertical alignment changes
    QTextCharFormat::VerticalAlignment va = f.verticalAlignment();
    if ( va == QTextCharFormat::AlignNormal ) {
        m_textSuper->setChecked( false );
        m_textSub->setChecked( false );
    } else if ( va == QTextCharFormat::AlignSuperScript ) {
        m_textSuper->setChecked( true );
    } else if ( va == QTextCharFormat::AlignSubScript ) {
        m_textSub->setChecked( true );
    }
}


void KNoteEdit::slotCursorPositionChanged()
{
    if (!acceptRichText())
        return;
    // alignment changes
    const Qt::Alignment a = alignment();
    if ( a & Qt::AlignLeft ) {
        m_textAlignLeft->setChecked( true );
    } else if ( a & Qt::AlignHCenter ) {
        m_textAlignCenter->setChecked( true );
    } else if ( a & Qt::AlignRight ) {
        m_textAlignRight->setChecked( true );
    } else if ( a & Qt::AlignJustify ) {
        m_textAlignBlock->setChecked( true );
    }
}

/** private methods **/

void KNoteEdit::autoIndent()
{
    QTextCursor c = textCursor();
    QTextBlock b = c.block();

    QString string;
    while ( ( b.previous().length() > 0 ) && string.trimmed().isEmpty() ) {
        b = b.previous();
        string = b.text();
    }

    if ( string.trimmed().isEmpty() ) {
        return;
    }

    // This routine returns the whitespace before the first non white space
    // character in string.
    // It is assumed that string contains at least one non whitespace character
    // ie \n \r \t \v \f and space
    QString indentString;

    const int len = string.length();
    int i = 0;
    while ( i < len && string.at( i ).isSpace() ) {
        indentString += string.at( i++ );
    }

    if ( !indentString.isEmpty() ) {
        c.insertText( indentString );
    }
}

void KNoteEdit::enableRichTextActions(bool enabled)
{
    m_textColor->setEnabled( enabled );
    m_textFont->setEnabled( enabled );
    m_textSize->setEnabled( enabled );

    m_textBold->setEnabled( enabled );
    m_textItalic->setEnabled( enabled );
    m_textUnderline->setEnabled( enabled );
    m_textStrikeOut->setEnabled( enabled );

    m_textAlignLeft->setEnabled( enabled );
    m_textAlignCenter->setEnabled( enabled );
    m_textAlignRight->setEnabled( enabled );
    m_textAlignBlock->setEnabled( enabled );

    m_textList->setEnabled( enabled );
    m_textSuper->setEnabled( enabled );
    m_textSub->setEnabled( enabled );

    m_textIncreaseIndent->setEnabled( enabled );
    m_textDecreaseIndent->setEnabled( enabled );
}

void KNoteEdit::slotInsertDate()
{
    NoteShared::NoteEditorUtils::insertDate(this);
}

void KNoteEdit::slotInsertCheckMark()
{
    QTextCursor cursor = textCursor();
    NoteShared::NoteEditorUtils::addCheckmark(cursor);
}

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

int KNoteEdit::cursorPositionFromStart() const
{
    return textCursor().position();
}
