/* This file is part of the KDE project
 * Copyright (C) 2007,2011 Jan Hambrecht <jaham@gmx.net>
 * Copyright (C) 2008 Rob Buis <buis@kde.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "ArtisticTextTool.h"
#include "ArtisticTextToolSelection.h"
#include "AttachTextToPathCommand.h"
#include "DetachTextFromPathCommand.h"
#include "AddTextRangeCommand.h"
#include "RemoveTextRangeCommand.h"
#include "ArtisticTextShapeConfigWidget.h"
#include "ArtisticTextShapeOnPathWidget.h"
#include "MoveStartOffsetStrategy.h"
#include "SelectTextStrategy.h"
#include "ChangeTextOffsetCommand.h"
#include "ChangeTextFontCommand.h"
#include "ChangeTextAnchorCommand.h"
#include "ReplaceTextRangeCommand.h"

#include <KoCanvasBase.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoPointerEvent.h>
#include <KoPathShape.h>
#include <KoShapeBackground.h>
#include <KoShapeController.h>
#include <KoShapeContainer.h>
#include <KoInteractionStrategy.h>
#include <KoIcon.h>

#include <klocale.h>
#include <kstandardaction.h>
#include <kaction.h>
#include <kdebug.h>

#include <QAction>
#include <QGridLayout>
#include <QToolButton>
#include <QCheckBox>
#include <QPainter>
#include <QPainterPath>
#include <kundo2command.h>

#include <float.h>
#include <math.h>

const int BlinkInterval = 500;

static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut)
{
    foreach (const QKeySequence & ks, KStandardShortcut::shortcut(shortcut).toList()) {
        if (input == ks)
            return true;
    }
    return false;
}

ArtisticTextTool::ArtisticTextTool(KoCanvasBase *canvas)
    : KoToolBase(canvas), m_selection(canvas, this), m_currentShape(0), m_hoverText(0), m_hoverPath(0), m_hoverHandle(false)
    , m_textCursor( -1 ), m_showCursor( true ), m_currentStrategy(0)
{
    m_detachPath  = new KAction(koIcon("artistictext-detach-path"), i18n("Detach Path"), this);
    m_detachPath->setEnabled( false );
    connect( m_detachPath, SIGNAL(triggered()), this, SLOT(detachPath()) );
    addAction("artistictext_detach_from_path", m_detachPath);

    m_convertText  = new KAction(koIcon("pathshape"), i18n("Convert to Path"), this);
    m_convertText->setEnabled( false );
    connect( m_convertText, SIGNAL(triggered()), this, SLOT(convertText()) );
    addAction("artistictext_convert_to_path", m_convertText);

    m_fontBold = new KAction(koIcon("format-text-bold"), i18n("Bold text"), this);
    m_fontBold->setCheckable(true);
    connect(m_fontBold, SIGNAL(toggled(bool)), this, SLOT(toggleFontBold(bool)));
    addAction("artistictext_font_bold", m_fontBold);

    m_fontItalic = new KAction(koIcon("format-text-italic"), i18n("Italic text"), this);
    m_fontItalic->setCheckable(true);
    connect(m_fontItalic, SIGNAL(toggled(bool)), this, SLOT(toggleFontItalic(bool)));
    addAction("artistictext_font_italic", m_fontItalic);

    m_superScript = new KAction(koIcon("format-text-superscript"), i18n("Superscript"), this);
    m_superScript->setCheckable(true);
    connect(m_superScript, SIGNAL(triggered()), this, SLOT(setSuperScript()));
    addAction("artistictext_superscript", m_superScript);

    m_subScript = new KAction(koIcon("format-text-subscript"), i18n("Subscript"), this);
    m_subScript->setCheckable(true);
    connect(m_subScript, SIGNAL(triggered()), this, SLOT(setSubScript()));
    addAction("artistictext_subscript", m_subScript);

    KAction *anchorStart = new KAction(koIcon("format-justify-left"), i18n("Anchor at Start"), this);
    anchorStart->setCheckable( true );
    anchorStart->setData(ArtisticTextShape::AnchorStart);
    addAction("artistictext_anchor_start", anchorStart);

    KAction *anchorMiddle = new KAction(koIcon("format-justify-center"), i18n("Anchor at Middle"), this);
    anchorMiddle->setCheckable( true );
    anchorMiddle->setData(ArtisticTextShape::AnchorMiddle);
    addAction("artistictext_anchor_middle", anchorMiddle);

    KAction *anchorEnd = new KAction(koIcon("format-justify-right"), i18n("Anchor at End"), this);
    anchorEnd->setCheckable( true );
    anchorEnd->setData(ArtisticTextShape::AnchorEnd);
    addAction("artistictext_anchor_end", anchorEnd);

    m_anchorGroup = new QActionGroup(this);
    m_anchorGroup->setExclusive(true);
    m_anchorGroup->addAction(anchorStart);
    m_anchorGroup->addAction(anchorMiddle);
    m_anchorGroup->addAction(anchorEnd);
    connect(m_anchorGroup, SIGNAL(triggered(QAction*)), this, SLOT(anchorChanged(QAction*)));

    KoShapeManager *manager = canvas->shapeManager();
    connect(manager, SIGNAL(selectionContentChanged()), this, SLOT(textChanged()));

    addAction("edit_select_all", KStandardAction::selectAll(this, SLOT(selectAll()), this));
    addAction("edit_deselect_all", KStandardAction::deselect(this, SLOT(deselectAll()), this));

    setTextMode(true);
}

ArtisticTextTool::~ArtisticTextTool()
{
    delete m_currentStrategy;
}

QTransform ArtisticTextTool::cursorTransform() const
{
    if (!m_currentShape)
        return QTransform();

    QTransform transform;

    const int textLength = m_currentShape->plainText().length();
    if (m_textCursor <= textLength) {
        const QPointF pos = m_currentShape->charPositionAt(m_textCursor);
        const qreal angle = m_currentShape->charAngleAt(m_textCursor);
        QFontMetrics metrics(m_currentShape->fontAt(m_textCursor));

        transform.translate( pos.x() - 1, pos.y() );
        transform.rotate( 360. - angle );
        transform.translate( 0, metrics.descent() );
    } else if (m_textCursor <= textLength + m_linefeedPositions.size()) {
        const QPointF pos = m_linefeedPositions.value(m_textCursor-textLength-1);
        QFontMetrics metrics(m_currentShape->fontAt(textLength-1));
        transform.translate(pos.x(), pos.y());
        transform.translate(0, metrics.descent());
    }

    return transform * m_currentShape->absoluteTransformation(0);
}

void ArtisticTextTool::paint( QPainter &painter, const KoViewConverter &converter)
{
    if (! m_currentShape)
        return;

    if (m_showCursor && m_blinkingCursor.isActive() && !m_currentStrategy) {
        painter.save();
        m_currentShape->applyConversion( painter, converter );
        painter.setBrush( Qt::black );
        painter.setWorldTransform( cursorTransform(), true );
        painter.setClipping( false );
        painter.drawPath( m_textCursorShape );
        painter.restore();
    }
    m_showCursor = !m_showCursor;

    if (m_currentShape->isOnPath()) {
        painter.save();
        m_currentShape->applyConversion( painter, converter );
        if (!m_currentShape->baselineShape()) {
            painter.setPen(Qt::DotLine);
            painter.setBrush(Qt::NoBrush);
            painter.drawPath(m_currentShape->baseline());
        }
        painter.setPen(Qt::blue);
        painter.setBrush(m_hoverHandle ? Qt::red : Qt::white);
        painter.drawPath(offsetHandleShape());
        painter.restore();
    }
    if (m_selection.hasSelection()) {
        painter.save();
        m_selection.paint(painter, converter);
        painter.restore();
    }
}

void ArtisticTextTool::repaintDecorations()
{
    canvas()->updateCanvas(offsetHandleShape().boundingRect());
    if (m_currentShape && m_currentShape->isOnPath()) {
        if (!m_currentShape->baselineShape())
            canvas()->updateCanvas(m_currentShape->baseline().boundingRect());
    }
    m_selection.repaintDecoration();
}

int ArtisticTextTool::cursorFromMousePosition(const QPointF &mousePosition)
{
    if (!m_currentShape)
        return -1;

    const QPointF pos = m_currentShape->documentToShape(mousePosition);
    const int len = m_currentShape->plainText().length();
    int hit = -1;
    qreal mindist = DBL_MAX;
    for ( int i = 0; i <= len;++i ) {
        QPointF center = pos - m_currentShape->charPositionAt(i);
        if ( (fabs(center.x()) + fabs(center.y())) < mindist ) {
            hit = i;
            mindist = fabs(center.x()) + fabs(center.y());
        }
    }
    return hit;
}

void ArtisticTextTool::mousePressEvent( KoPointerEvent *event )
{
    if (m_hoverHandle) {
        m_currentStrategy = new MoveStartOffsetStrategy(this, m_currentShape);
    }
    if (m_hoverText) {
        KoSelection *selection = canvas()->shapeManager()->selection();
        if(m_hoverText != m_currentShape) {
            // if we hit another text shape, select that shape
            selection->deselectAll();
            setCurrentShape(m_hoverText);
            selection->select( m_currentShape );
        }
        // change the text cursor position
        int hitCursorPos = cursorFromMousePosition(event->point);
        if (hitCursorPos >= 0) {
            setTextCursorInternal(hitCursorPos);
            m_selection.clear();
        }
        m_currentStrategy = new SelectTextStrategy(this, m_textCursor);
    }
    event->ignore();
}

void ArtisticTextTool::mouseMoveEvent( KoPointerEvent *event )
{
    m_hoverPath = 0;
    m_hoverText = 0;

    if (m_currentStrategy) {
        m_currentStrategy->handleMouseMove(event->point, event->modifiers());
        return;
    }

    const bool textOnPath = m_currentShape && m_currentShape->isOnPath();
    if (textOnPath) {
        QPainterPath handle = offsetHandleShape();
        QPointF handleCenter = handle.boundingRect().center();
        if (handleGrabRect(event->point).contains(handleCenter)) {
            // mouse is on offset handle
            if (!m_hoverHandle)
                canvas()->updateCanvas(handle.boundingRect());
            m_hoverHandle = true;
        } else {
            if (m_hoverHandle)
                canvas()->updateCanvas(handle.boundingRect());
            m_hoverHandle = false;
        }
    }
    if (!m_hoverHandle) {
        // find text or path shape at cursor position
        QList<KoShape*> shapes = canvas()->shapeManager()->shapesAt( handleGrabRect(event->point) );
        if (shapes.contains(m_currentShape)) {
            m_hoverText = m_currentShape;
        } else {
            foreach( KoShape * shape, shapes ) {
                ArtisticTextShape * text = dynamic_cast<ArtisticTextShape*>( shape );
                if (text && !m_hoverText) {
                    m_hoverText = text;
                }
                KoPathShape * path = dynamic_cast<KoPathShape*>( shape );
                if (path && !m_hoverPath) {
                    m_hoverPath = path;
                }
                if(m_hoverPath && m_hoverText)
                    break;
            }
        }
    }

    const bool hoverOnBaseline = textOnPath && m_currentShape->baselineShape() == m_hoverPath;
    // update cursor and status text
    if ( m_hoverText ) {
        useCursor( QCursor( Qt::IBeamCursor ) );
        if (m_hoverText == m_currentShape)
            emit statusTextChanged(i18n("Click to change cursor position."));
        else
            emit statusTextChanged(i18n("Click to select text shape."));
    } else if( m_hoverPath && m_currentShape && !hoverOnBaseline) {
        useCursor( QCursor( Qt::PointingHandCursor ) );
        emit statusTextChanged(i18n("Double click to put text on path."));
    } else  if (m_hoverHandle) {
        useCursor( QCursor( Qt::ArrowCursor ) );
        emit statusTextChanged( i18n("Drag handle to change start offset.") );
    } else {
        useCursor( QCursor( Qt::ArrowCursor ) );
        if (m_currentShape)
            emit statusTextChanged( i18n("Press escape to finish editing.") );
        else
            emit statusTextChanged("");
    }
}

void ArtisticTextTool::mouseReleaseEvent( KoPointerEvent *event )
{
    if (m_currentStrategy) {
        m_currentStrategy->finishInteraction(event->modifiers());
        KUndo2Command *cmd = m_currentStrategy->createCommand();
        if (cmd)
            canvas()->addCommand(cmd);
        delete m_currentStrategy;
        m_currentStrategy = 0;
    }
    updateActions();
}

void ArtisticTextTool::shortcutOverrideEvent(QKeyEvent *event)
{
    QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers()));
    if (hit(item, KStandardShortcut::Begin) ||
        hit(item, KStandardShortcut::End)) {
        event->accept();
    }
}

void ArtisticTextTool::mouseDoubleClickEvent(KoPointerEvent */*event*/)
{
    if (m_hoverPath && m_currentShape) {
        if (!m_currentShape->isOnPath() || m_currentShape->baselineShape() != m_hoverPath) {
            m_blinkingCursor.stop();
            m_showCursor = false;
            updateTextCursorArea();
            canvas()->addCommand( new AttachTextToPathCommand( m_currentShape, m_hoverPath ) );
            m_blinkingCursor.start( BlinkInterval );
            updateActions();
            m_hoverPath = 0;
            m_linefeedPositions.clear();
            return;
        }
    }
}

void ArtisticTextTool::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Escape) {
        event->ignore();
        return;
    }

    event->accept();
    if ( m_currentShape && textCursor() > -1 ) {
        switch(event->key())
        {
        case Qt::Key_Delete:
            if (m_selection.hasSelection()) {
                removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount());
            } else if( textCursor() >= 0 && textCursor() < m_currentShape->plainText().length()) {
                removeFromTextCursor( textCursor(), 1 );
            }
            break;
        case Qt::Key_Backspace:
            if (m_selection.hasSelection()) {
                removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount());
            } else {
                removeFromTextCursor( textCursor()-1, 1 );
            }
            break;
        case Qt::Key_Right:
            if (event->modifiers() & Qt::ShiftModifier) {
                int selectionStart, selectionEnd;
                if (m_selection.hasSelection()) {
                    selectionStart = m_selection.selectionStart();
                    selectionEnd = selectionStart + m_selection.selectionCount();
                    if (textCursor() == selectionStart)
                        selectionStart = textCursor()+1;
                    else if(textCursor() == selectionEnd)
                        selectionEnd = textCursor()+1;
                } else {
                    selectionStart = textCursor();
                    selectionEnd = textCursor()+1;
                }
                m_selection.selectText(selectionStart, selectionEnd);
            } else {
                m_selection.clear();
            }
            setTextCursor(m_currentShape, textCursor() + 1);
            break;
        case Qt::Key_Left:
            if (event->modifiers() & Qt::ShiftModifier) {
                int selectionStart, selectionEnd;
                if (m_selection.hasSelection()) {
                    selectionStart = m_selection.selectionStart();
                    selectionEnd = selectionStart + m_selection.selectionCount();
                    if (textCursor() == selectionStart)
                        selectionStart = textCursor()-1;
                    else if(textCursor() == selectionEnd)
                        selectionEnd = textCursor()-1;
                } else {
                    selectionEnd = textCursor();
                    selectionStart = textCursor()-1;
                }
                m_selection.selectText(selectionStart, selectionEnd);
            } else {
                m_selection.clear();
            }
            setTextCursor(m_currentShape, textCursor() - 1);
            break;
        case Qt::Key_Home:
            if (event->modifiers() & Qt::ShiftModifier) {
                const int selectionStart = 0;
                const int selectionEnd = m_selection.hasSelection() ? m_selection.selectionStart()+m_selection.selectionCount() : m_textCursor;
                m_selection.selectText(selectionStart, selectionEnd);
            } else {
                m_selection.clear();
            }
            setTextCursor(m_currentShape, 0);
            break;
        case Qt::Key_End:
            if (event->modifiers() & Qt::ShiftModifier) {
                const int selectionStart = m_selection.hasSelection() ? m_selection.selectionStart() : m_textCursor;
                const int selectionEnd = m_currentShape->plainText().length();
                m_selection.selectText(selectionStart, selectionEnd);
            } else {
                m_selection.clear();
            }
            setTextCursor(m_currentShape, m_currentShape->plainText().length());
            break;
        case Qt::Key_Return:
        case Qt::Key_Enter:
        {
            const int textLength = m_currentShape->plainText().length();
            if (m_textCursor >= textLength) {
                // get font metrics for last character
                QFontMetrics metrics(m_currentShape->fontAt(textLength-1));
                const qreal offset = m_currentShape->size().height() + (m_linefeedPositions.size() + 1) * metrics.height();
                m_linefeedPositions.append(QPointF(0, offset));
                setTextCursor(m_currentShape, textCursor()+1);
            }
            break;
        }
        default:
            if (event->text().isEmpty()) {
                event->ignore();
                return;
            }
            if (m_selection.hasSelection()) {
                removeFromTextCursor(m_selection.selectionStart(), m_selection.selectionCount());
            }
            addToTextCursor( event->text() );
        }
    } else {
        event->ignore();
    }
}

void ArtisticTextTool::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
    Q_UNUSED(toolActivation);
    foreach (KoShape *shape, shapes) {
        ArtisticTextShape *text = dynamic_cast<ArtisticTextShape*>( shape );
        if(text) {
            setCurrentShape(text);
            break;
        }
    }
    if(!m_currentShape) {
        // none found
        emit done();
        return;
    }

    m_hoverText = 0;
    m_hoverPath = 0;

    updateActions();
    emit statusTextChanged( i18n("Press return to finish editing.") );
    repaintDecorations();

    KoShapeManager *manager = canvas()->shapeManager();
    connect(manager, SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
}

void ArtisticTextTool::blinkCursor()
{
    updateTextCursorArea();
}

void ArtisticTextTool::deactivate()
{
    if ( m_currentShape ) {
        if( m_currentShape->plainText().isEmpty() ) {
            canvas()->addCommand( canvas()->shapeController()->removeShape( m_currentShape ) );
        }
        setCurrentShape(0);
    }
    m_hoverPath = 0;
    m_hoverText = 0;

    KoShapeManager *manager = canvas()->shapeManager();
    disconnect(manager, SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
}

void ArtisticTextTool::updateActions()
{
    if( m_currentShape ) {
        const QFont font = m_currentShape->fontAt(textCursor());
        const CharIndex index = m_currentShape->indexOfChar(textCursor());
        ArtisticTextRange::BaselineShift baselineShift = ArtisticTextRange::None;
        if (index.first >= 0) {
            baselineShift = m_currentShape->text().at(index.first).baselineShift();
        }
        m_fontBold->blockSignals(true);
        m_fontBold->setChecked(font.bold());
        m_fontBold->blockSignals(false);
        m_fontBold->setEnabled(true);
        m_fontItalic->blockSignals(true);
        m_fontItalic->setChecked(font.italic());
        m_fontItalic->blockSignals(false);
        m_fontItalic->setEnabled(true);
        m_detachPath->setEnabled( m_currentShape->isOnPath() );
        m_convertText->setEnabled( true );
        m_anchorGroup->blockSignals(true);
        foreach(QAction *action, m_anchorGroup->actions()) {
            if (action->data().toInt() == m_currentShape->textAnchor())
                action->setChecked(true);
        }
        m_anchorGroup->blockSignals(false);
        m_anchorGroup->setEnabled(true);
        m_superScript->blockSignals(true);
        m_superScript->setChecked(baselineShift == ArtisticTextRange::Super);
        m_superScript->blockSignals(false);
        m_subScript->blockSignals(true);
        m_subScript->setChecked(baselineShift == ArtisticTextRange::Sub);
        m_subScript->blockSignals(false);
        m_superScript->setEnabled(true);
        m_subScript->setEnabled(true);
    } else {
        m_detachPath->setEnabled(false);
        m_convertText->setEnabled(false);
        m_fontBold->setEnabled(false);
        m_fontItalic->setEnabled(false);
        m_anchorGroup->setEnabled(false);
        m_superScript->setEnabled(false);
        m_subScript->setEnabled(false);
    }
}

void ArtisticTextTool::detachPath()
{
    if( m_currentShape && m_currentShape->isOnPath() ) {
        canvas()->addCommand( new DetachTextFromPathCommand( m_currentShape ) );
        updateActions();
    }
}

void ArtisticTextTool::convertText()
{
    if( ! m_currentShape )
        return;

    KoPathShape * path = KoPathShape::createShapeFromPainterPath( m_currentShape->outline() );
    path->setParent( m_currentShape->parent() );
    path->setZIndex( m_currentShape->zIndex() );
    path->setStroke( m_currentShape->stroke() );
    path->setBackground( m_currentShape->background() );
    path->setTransformation( m_currentShape->transformation() );
    path->setShapeId( KoPathShapeId );

    KUndo2Command * cmd = canvas()->shapeController()->addShapeDirect( path );
    cmd->setText( kundo2_i18n("Convert to Path") );
    canvas()->shapeController()->removeShape( m_currentShape, cmd );
    canvas()->addCommand( cmd );

    emit done();
}

QList<QPointer<QWidget> > ArtisticTextTool::createOptionWidgets()
{
    QList<QPointer<QWidget> > widgets;

    ArtisticTextShapeConfigWidget * configWidget = new ArtisticTextShapeConfigWidget(this);
    configWidget->setObjectName("ArtisticTextConfigWidget");
    configWidget->setWindowTitle(i18n("Text Properties"));
    connect(configWidget, SIGNAL(fontFamilyChanged(QFont)), this, SLOT(setFontFamiliy(QFont)));
    connect(configWidget, SIGNAL(fontSizeChanged(int)), this, SLOT(setFontSize(int)));
    connect(this, SIGNAL(shapeSelected()), configWidget, SLOT(updateWidget()));
    connect(canvas()->shapeManager(), SIGNAL(selectionContentChanged()),
            configWidget, SLOT(updateWidget()));
    widgets.append(configWidget);

    ArtisticTextShapeOnPathWidget *pathWidget = new ArtisticTextShapeOnPathWidget(this);
    pathWidget->setObjectName("ArtisticTextPathWidget");
    pathWidget->setWindowTitle(i18n("Text On Path"));
    connect(pathWidget, SIGNAL(offsetChanged(int)), this, SLOT(setStartOffset(int)));
    connect(this, SIGNAL(shapeSelected()), pathWidget, SLOT(updateWidget()));
    connect(canvas()->shapeManager(), SIGNAL(selectionContentChanged()),
            pathWidget, SLOT(updateWidget()));
    widgets.append(pathWidget);

    if (m_currentShape) {
        pathWidget->updateWidget();
        configWidget->updateWidget();
    }

    return widgets;
}

KoToolSelection *ArtisticTextTool::selection()
{
    return &m_selection;
}

void ArtisticTextTool::enableTextCursor( bool enable )
{
    if ( enable ) {
        if( m_currentShape )
            setTextCursorInternal( m_currentShape->plainText().length() );
        connect( &m_blinkingCursor, SIGNAL(timeout()), this, SLOT(blinkCursor()) );
        m_blinkingCursor.start( BlinkInterval );
    } else {
        m_blinkingCursor.stop();
        disconnect( &m_blinkingCursor, SIGNAL(timeout()), this, SLOT(blinkCursor()) );
        setTextCursorInternal( -1 );
        m_showCursor = false;
    }
}

void ArtisticTextTool::setTextCursor(ArtisticTextShape *textShape, int textCursor)
{
    if (!m_currentShape || textShape != m_currentShape)
        return;
    if (m_textCursor == textCursor || textCursor < 0)
        return;
    const int textLength = m_currentShape->plainText().length() + m_linefeedPositions.size();
    if (textCursor > textLength)
        return;
    setTextCursorInternal(textCursor);
}

int ArtisticTextTool::textCursor() const
{
    return m_textCursor;
}

void ArtisticTextTool::updateTextCursorArea() const
{
    if( ! m_currentShape || m_textCursor < 0 )
        return;

    QRectF bbox = cursorTransform().mapRect( m_textCursorShape.boundingRect() );
    canvas()->updateCanvas( bbox );
}

void ArtisticTextTool::setCurrentShape(ArtisticTextShape *currentShape)
{
    if (m_currentShape == currentShape)
        return;
    enableTextCursor( false );
    m_currentShape = currentShape;
    m_selection.setSelectedShape(m_currentShape);
    if (m_currentShape)
        enableTextCursor( true );
    emit shapeSelected();
}

void ArtisticTextTool::setTextCursorInternal( int textCursor )
{
    updateTextCursorArea();
    m_textCursor = textCursor;
    createTextCursorShape();
    updateTextCursorArea();
    updateActions();
    emit shapeSelected();
}

void ArtisticTextTool::createTextCursorShape()
{
    if (m_textCursor < 0 || ! m_currentShape)
        return;
    const QRectF extents = m_currentShape->charExtentsAt(m_textCursor);
    m_textCursorShape = QPainterPath();
    m_textCursorShape.addRect( 0, 0, 1, -extents.height() );
    m_textCursorShape.closeSubpath();
}

void ArtisticTextTool::removeFromTextCursor( int from, unsigned int count )
{
    if ( from >= 0 ) {
        if (m_selection.hasSelection()) {
            // clear selection before text is removed, or else selection will be invalid
            m_selection.clear();
        }
        KUndo2Command *cmd = new RemoveTextRangeCommand(this, m_currentShape, from, count);
        canvas()->addCommand( cmd );
    }
}

void ArtisticTextTool::addToTextCursor( const QString &str )
{
    if ( !str.isEmpty() && m_textCursor > -1 ) {
        QString printable;
        for ( int i = 0;i < str.length();i++ ) {
            if ( str[i].isPrint() )
                printable.append( str[i] );
        }
        unsigned int len = printable.length();
        if ( len ) {
            const int textLength = m_currentShape->plainText().length();
            if (m_textCursor <= textLength) {
                KUndo2Command *cmd = new AddTextRangeCommand(this, m_currentShape, printable, m_textCursor);
                canvas()->addCommand( cmd );
            } else if (m_textCursor > textLength && m_textCursor <= textLength + m_linefeedPositions.size()) {
                const QPointF pos = m_linefeedPositions.value(m_textCursor-textLength-1);
                ArtisticTextRange newLine(printable, m_currentShape->fontAt(textLength-1));
                newLine.setXOffsets(QList<qreal>() << pos.x(), ArtisticTextRange::AbsoluteOffset);
                newLine.setYOffsets(QList<qreal>() << pos.y()-m_currentShape->baselineOffset(), ArtisticTextRange::AbsoluteOffset);
                KUndo2Command *cmd = new AddTextRangeCommand(this, m_currentShape, newLine, m_textCursor);
                canvas()->addCommand( cmd );
                m_linefeedPositions.clear();
            }
        }
    }
}

void ArtisticTextTool::textChanged()
{
    if ( !m_currentShape)
        return;

    const QString currentText = m_currentShape->plainText();
    if (m_textCursor > currentText.length())
        setTextCursorInternal(currentText.length());
}

void ArtisticTextTool::shapeSelectionChanged()
{
    KoSelection *selection = canvas()->shapeManager()->selection();
    if (selection->isSelected(m_currentShape))
        return;

    foreach (KoShape *shape, selection->selectedShapes()) {
        ArtisticTextShape *text = dynamic_cast<ArtisticTextShape*>(shape);
        if(text) {
            setCurrentShape(text);
            break;
        }
    }
}

QPainterPath ArtisticTextTool::offsetHandleShape()
{
    QPainterPath handle;
    if (!m_currentShape || !m_currentShape->isOnPath())
        return handle;

    const QPainterPath baseline = m_currentShape->baseline();
    const qreal offset = m_currentShape->startOffset();
    QPointF offsetPoint = baseline.pointAtPercent(offset);
    QSizeF paintSize = handlePaintRect(QPointF()).size();

    handle.moveTo(0, 0);
    handle.lineTo(0.5*paintSize.width(), paintSize.height());
    handle.lineTo(-0.5*paintSize.width(), paintSize.height());
    handle.closeSubpath();

    QTransform transform;
    transform.translate( offsetPoint.x(), offsetPoint.y() );
    transform.rotate(360. - baseline.angleAtPercent(offset));

    return transform.map(handle);
}

void ArtisticTextTool::setStartOffset(int offset)
{
    if (!m_currentShape || !m_currentShape->isOnPath())
        return;

    const qreal newOffset = static_cast<qreal>(offset) / 100.0;
    if( newOffset != m_currentShape->startOffset() ) {
        canvas()->addCommand(new ChangeTextOffsetCommand(m_currentShape, m_currentShape->startOffset(), newOffset));
    }
}

void ArtisticTextTool::changeFontProperty(FontProperty property, const QVariant &value)
{
    if (!m_currentShape || !m_selection.hasSelection())
        return;

    // build font ranges
    const int selectedCharCount = m_selection.selectionCount();
    const int selectedCharStart = m_selection.selectionStart();
    QList<ArtisticTextRange> ranges = m_currentShape->text();
    CharIndex index = m_currentShape->indexOfChar(selectedCharStart);
    if (index.first < 0)
        return;

    KUndo2Command * cmd = new KUndo2Command;
    int collectedCharCount = 0;
    while(collectedCharCount < selectedCharCount) {
        ArtisticTextRange &range = ranges[index.first];
        QFont font = range.font();
        switch(property) {
        case BoldProperty:
            font.setBold(value.toBool());
            break;
        case ItalicProperty:
            font.setItalic(value.toBool());
            break;
        case FamiliyProperty:
            font.setFamily(value.toString());
            break;
        case SizeProperty:
            font.setPointSize(value.toInt());
            break;
        }

        const int changeCount = qMin(selectedCharCount-collectedCharCount, range.text().count()-index.second);
        const int changeStart = selectedCharStart+collectedCharCount;
        new ChangeTextFontCommand(m_currentShape, changeStart, changeCount, font, cmd);
        index.first++;
        index.second = 0;
        collectedCharCount += changeCount;
    }

    canvas()->addCommand(cmd);
}

void ArtisticTextTool::toggleFontBold(bool enabled)
{
    changeFontProperty(BoldProperty, QVariant(enabled));
}

void ArtisticTextTool::toggleFontItalic(bool enabled)
{
    changeFontProperty(ItalicProperty, QVariant(enabled));
}

void ArtisticTextTool::anchorChanged(QAction* action)
{
    if (!m_currentShape)
        return;

    ArtisticTextShape::TextAnchor newAnchor = static_cast<ArtisticTextShape::TextAnchor>(action->data().toInt());
    if (newAnchor != m_currentShape->textAnchor()) {
        canvas()->addCommand(new ChangeTextAnchorCommand(m_currentShape, newAnchor));
    }
}

void ArtisticTextTool::setFontFamiliy(const QFont &font)
{
    changeFontProperty(FamiliyProperty, QVariant(font.family()));
}

void ArtisticTextTool::setFontSize(int size)
{
    changeFontProperty(SizeProperty, QVariant(size));
}

void ArtisticTextTool::setSuperScript()
{
    toggleSubSuperScript(ArtisticTextRange::Super);
}

void ArtisticTextTool::setSubScript()
{
    toggleSubSuperScript(ArtisticTextRange::Sub);
}

void ArtisticTextTool::toggleSubSuperScript(ArtisticTextRange::BaselineShift mode)
{
    if (!m_currentShape || !m_selection.hasSelection())
        return;

    const int from = m_selection.selectionStart();
    const int count = m_selection.selectionCount();

    QList<ArtisticTextRange> ranges = m_currentShape->copyText(from, count);
    const int rangeCount = ranges.count();
    if (!rangeCount)
        return;

    // determine if we want to disable the specified mode
    const bool disableMode = ranges.first().baselineShift() == mode;
    const qreal fontSize = m_currentShape->defaultFont().pointSizeF();

    for (int i = 0; i < rangeCount; ++i) {
        ArtisticTextRange &currentRange = ranges[i];
        QFont font = currentRange.font();
        if (disableMode) {
            currentRange.setBaselineShift(ArtisticTextRange::None);
            font.setPointSizeF(fontSize);
        } else {
            currentRange.setBaselineShift(mode);
            font.setPointSizeF(fontSize*ArtisticTextRange::subAndSuperScriptSizeFactor());
        }
        currentRange.setFont(font);
    }
    canvas()->addCommand(new ReplaceTextRangeCommand(m_currentShape, ranges, from, count, this));
}

void ArtisticTextTool::selectAll()
{
    if (m_currentShape) {
        m_selection.selectText(0, m_currentShape->plainText().count());
    }
}

void ArtisticTextTool::deselectAll()
{
    if (m_currentShape) {
        m_selection.clear();
    }
}

QVariant ArtisticTextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const
{
    if (!m_currentShape)
        return QVariant();

    switch (query) {
    case Qt::ImMicroFocus: {
        // The rectangle covering the area of the input cursor in widget coordinates.
        QRectF rect = m_textCursorShape.boundingRect();
        rect.moveTop(rect.bottom());
        QTransform shapeMatrix = m_currentShape->absoluteTransformation(&converter);
        qreal zoomX, zoomY;
        converter.zoom(&zoomX, &zoomY);
        shapeMatrix.scale(zoomX, zoomY);
        rect = shapeMatrix.mapRect(rect);
        return rect.toRect();
    }
    case Qt::ImFont:
        // The currently used font for text input.
        return m_currentShape->fontAt(m_textCursor);
    case Qt::ImCursorPosition:
        // The logical position of the cursor within the text surrounding the input area (see ImSurroundingText).
        return m_currentShape->charPositionAt(m_textCursor);
    case Qt::ImSurroundingText:
        // The plain text around the input area, for example the current paragraph.
        return m_currentShape->plainText();
    case Qt::ImCurrentSelection:
        // The currently selected text.
        return QVariant();
    default:
        ; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition
    }
    return QVariant();
}

#include <ArtisticTextTool.moc>
