/***************************************************************************
 *   Copyright (C) 2005 by Enrico Ros <eros.kde@email.it>                  *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#include "pageviewannotator.h"

// qt / kde includes
#include <qfile.h>
#include <qcolor.h>
#include <qevent.h>
#include <qlist.h>
#include <qpainter.h>
#include <qset.h>
#include <qvariant.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kstandarddirs.h>
#include <kinputdialog.h>
#include <kuser.h>
#include <kdebug.h>
#include <kmenu.h>

// system includes
#include <math.h>

// local includes
#include "core/area.h"
#include "core/document.h"
#include "core/page.h"
#include "core/annotations.h"
#include "settings.h"
#include "annotationtools.h"
#include "pageview.h"

/** @short PickPointEngine */
class PickPointEngine : public AnnotatorEngine
{
    public:
        PickPointEngine( const QDomElement & engineElement )
            : AnnotatorEngine( engineElement ), clicked( false ), pixmap( 0 ),
              xscale( 1.0 ), yscale( 1.0 )
        {
            // parse engine specific attributes
            pixmapName = engineElement.attribute( "hoverIcon" );
            QString stampname = m_annotElement.attribute( "icon" );
            if ( m_annotElement.attribute( "type" ) == "Stamp" && !stampname.simplified().isEmpty() )
                pixmapName = stampname;
            center = QVariant( engineElement.attribute( "center" ) ).toBool();
            bool ok = true;
            size = engineElement.attribute( "size", "32" ).toInt( &ok );
            if ( !ok )
                size = 32;
            m_block = QVariant( engineElement.attribute( "block" ) ).toBool();

            // create engine objects
            if ( !pixmapName.simplified().isEmpty() )
                pixmap = new QPixmap( DesktopIcon( pixmapName, size ) );
        }

        ~PickPointEngine()
        {
            delete pixmap;
        }

        QRect event( EventType type, Button button, double nX, double nY, double xScale, double yScale, const Okular::Page * page )
        {
            xscale=xScale;
            yscale=yScale;
            pagewidth = page->width();
            pageheight = page->height();
            // only proceed if pressing left button
            if ( button != Left )
                return QRect();

            // start operation on click
            if ( type == Press && clicked == false )
            {
                clicked = true;
                startpoint.x=nX;
                startpoint.y=nY;
            }
            // repaint if moving while pressing
            else if ( type == Move && clicked == true )
            {
            }
            // operation finished on release
            else if ( type == Release && clicked == true )
            {
                m_creationCompleted = true;
            }
            else
                return QRect();

            // update variables and extents (zoom invariant rect)
            point.x = nX;
            point.y = nY;
            if ( center && pixmap )
            {
                rect.left = nX - ( pixmap->width() / ( xScale * 2.0 ) );
                rect.top = nY - ( pixmap->height() / ( yScale * 2.0 ) );
            }
            else
            {
                rect.left = nX;
                rect.top = nY;
            }
            rect.right = rect.left + ( pixmap ? pixmap->width() / xScale : 0 );
            rect.bottom = rect.top + ( pixmap ? pixmap->height() / yScale : 0 );
            QRect boundrect = rect.geometry( (int)xScale, (int)yScale ).adjusted( 0, 0, 1, 1 );
            if ( m_block )
            {
                Okular::NormalizedRect tmprect( qMin( startpoint.x, point.x ), qMin( startpoint.y, point.y ), qMax( startpoint.x, point.x ), qMax( startpoint.y, point.y ) );
                boundrect |= tmprect.geometry( (int)xScale, (int)yScale ).adjusted( 0, 0, 1, 1 );
            }
            return boundrect;
        }

        void paint( QPainter * painter, double xScale, double yScale, const QRect & /*clipRect*/ )
        {
            if ( clicked )
            {
                if ( m_block )
                {
                    QPen origpen = painter->pen();
                    QPen pen = painter->pen();
                    pen.setStyle( Qt::DashLine );
                    painter->setPen( pen );
                    Okular::NormalizedRect tmprect( qMin( startpoint.x, point.x ), qMin( startpoint.y, point.y ), qMax( startpoint.x, point.x ), qMax( startpoint.y, point.y ) );
                    QRect realrect = tmprect.geometry( (int)xScale, (int)yScale );
                    painter->drawRect( realrect );
                    painter->setPen( origpen );
                }
                if ( pixmap )
                    painter->drawPixmap( QPointF( rect.left * xScale, rect.top * yScale ), *pixmap );
            }
        }

        QList< Okular::Annotation* > end()
        {
            m_creationCompleted = false;

            // find out annotation's description node
            if ( m_annotElement.isNull() )
                return QList< Okular::Annotation* >();

            // find out annotation's type
            Okular::Annotation * ann = 0;
            QString typeString = m_annotElement.attribute( "type" );
            // create TextAnnotation from path
            if ( typeString == "FreeText")	//<annotation type="Text"
            {
                //note dialog
                QString prompt = i18n( "Text of the new note:" );
                bool resok;
                QString note = KInputDialog::getMultiLineText( i18n( "New Text Note" ), prompt, QString(), &resok );
                if(resok)
                {
                    //add note
                    Okular::TextAnnotation * ta = new Okular::TextAnnotation();
                    ann = ta;
                    ta->setInplaceText( note );
                    ta->setTextType( Okular::TextAnnotation::InPlace );
                    //set boundary
                    rect.left = qMin(startpoint.x,point.x);
                    rect.top = qMin(startpoint.y,point.y);
                    rect.right = qMax(startpoint.x,point.x);
                    rect.bottom = qMax(startpoint.y,point.y);
                    kDebug().nospace() << "xyScale=" << xscale << "," << yscale;
                    static int padding = 2;
                    QFontMetricsF mf(ta->textFont());
                    QRectF rcf = mf.boundingRect( Okular::NormalizedRect( rect.left, rect.top, 1.0, 1.0 ).geometry( (int)pagewidth, (int)pageheight ).adjusted( padding, padding, -padding, -padding ),
                                                  Qt::AlignTop | Qt::AlignLeft | Qt::TextWordWrap, ta->inplaceText() );
                    rect.right = qMax(rect.right, rect.left+(rcf.width()+padding*2)/pagewidth);
                    rect.bottom = qMax(rect.bottom, rect.top+(rcf.height()+padding*2)/pageheight);
                    ta->setBoundingRectangle( this->rect );
                    ta->window().setSummary( "TextBox" );
                }
            }
            else if ( typeString == "Text")
            {
                Okular::TextAnnotation * ta = new Okular::TextAnnotation();
                ann = ta;
                ta->setTextType( Okular::TextAnnotation::Linked );
                ta->window().setText( QString() );
                //ta->window.flags &= ~(Okular::Annotation::Hidden);
                double iconhei=0.03;
                rect.left = point.x;
                rect.top = point.y;
                rect.right=rect.left+iconhei;
                rect.bottom=rect.top+iconhei*xscale/yscale;
                ta->setBoundingRectangle( this->rect );
                ta->window().setSummary( "Note" );
            }
            // create StampAnnotation from path
            else if ( typeString == "Stamp" )
            {
                Okular::StampAnnotation * sa = new Okular::StampAnnotation();
                ann = sa;
                sa->setStampIconName( pixmapName );
                double stampxscale = size / xscale;
                double stampyscale = size / yscale;
                if ( center )
                {
                    rect.left = point.x - stampxscale / 2;
                    rect.top = point.y - stampyscale / 2;
                }
                else
                {
                    rect.left = point.x;
                    rect.top = point.y;
                }
                rect.right = rect.left + stampxscale;
                rect.bottom = rect.top + stampyscale;
                sa->setBoundingRectangle( rect );
            }
            // create GeomAnnotation
            else if ( typeString == "GeomSquare" || typeString == "GeomCircle" )
            {
                Okular::GeomAnnotation * ga = new Okular::GeomAnnotation();
                ann = ga;
                // set the type
                if ( typeString == "GeomSquare" )
                    ga->setGeometricalType( Okular::GeomAnnotation::InscribedSquare );
                else
                    ga->setGeometricalType( Okular::GeomAnnotation::InscribedCircle );
                ga->style().setWidth( 5 );
                //set boundary
                rect.left = qMin( startpoint.x, point.x );
                rect.top = qMin( startpoint.y, point.y );
                rect.right = qMax( startpoint.x, point.x );
                rect.bottom = qMax( startpoint.y, point.y );
                ga->setBoundingRectangle( rect );
            }

            // safety check
            if ( !ann )
                return QList< Okular::Annotation* >();

            // set common attributes
            ann->style().setColor( m_annotElement.hasAttribute( "color" ) ?
                    m_annotElement.attribute( "color" ) : m_engineColor );
            if ( m_annotElement.hasAttribute( "opacity" ) )
                ann->style().setOpacity( m_annotElement.attribute( "opacity", "1.0" ).toDouble() );

            // return annotation
            return QList< Okular::Annotation* >() << ann;
        }

    private:
        bool clicked;
        Okular::NormalizedRect rect;
        Okular::NormalizedPoint startpoint;
        Okular::NormalizedPoint point;
        QPixmap * pixmap;
        QString pixmapName;
        int size;
        double xscale,yscale;
        double pagewidth, pageheight;
        bool center;
        bool m_block;
};

/** @short PolyLineEngine */
class PolyLineEngine : public AnnotatorEngine
{
    public:
        PolyLineEngine( const QDomElement & engineElement )
            : AnnotatorEngine( engineElement ), last( false )
        {
            // parse engine specific attributes
            m_block = engineElement.attribute( "block" ) == "true";
            bool ok = true;
            // numofpoints represents the max number of points for the current
            // polygon/polyline, with a pair of exceptions:
            // -1 means: the polyline must close on the first point (polygon)
            // 0 means: construct as many points as you want, right-click
            //   to construct the last point
            numofpoints = engineElement.attribute( "points" ).toInt( &ok );
            if ( !ok )
                numofpoints = -1;
        }

        QRect event( EventType type, Button button, double nX, double nY, double xScale, double yScale, const Okular::Page * /*page*/ )
        {
            // only proceed if pressing left button
//            if ( button != Left )
//                return rect;

            // start operation
            if ( type == Press )
            {
                newPoint.x = nX;
                newPoint.y = nY;
                if ( button == Right )
                    last = true;
            }
            // move the second point
            else if ( type == Move )
            {
                movingpoint.x = nX;
                movingpoint.y = nY;
                QRect oldmovingrect = movingrect;
                movingrect = rect | QRect( (int)( movingpoint.x * xScale ), (int)( movingpoint.y * yScale ), 1, 1 );
                return oldmovingrect | movingrect;
            }
            else if ( type == Release )
            {
                Okular::NormalizedPoint tmppoint;
                tmppoint.x = nX;
                tmppoint.y = nY;
                if ( fabs( tmppoint.x - newPoint.x + tmppoint.y - newPoint.y ) > 1e-2 )
                    return rect;

                if ( numofpoints == -1 && points.count() > 1 && ( fabs( points[0].x - newPoint.x + points[0].y - newPoint.y ) < 1e-2 ) )
                {
                    last = true;
                }
                else
                {
                    points.append( newPoint );
                    rect |= QRect( (int)( newPoint.x * xScale ), (int)( newPoint.y * yScale ), 1, 1 );
                }
                // end creation if we have constructed the last point of enough points
                if ( last || points.count() == numofpoints )
                {
                    m_creationCompleted = true;
                    last = false;
                    normRect = Okular::NormalizedRect( rect, xScale, yScale );
                }
            }

            return rect;
        }

        void paint( QPainter * painter, double xScale, double yScale, const QRect & /*clipRect*/ )
        {
            if ( points.count() < 1 )
                return;

            if ( m_block && points.count() == 2 )
            {
                Okular::NormalizedPoint first = points[0];
                Okular::NormalizedPoint second = points[1];
                // draw a semitransparent block around the 2 points
                painter->setPen( m_engineColor );
                painter->setBrush( QBrush( m_engineColor.light(), Qt::Dense4Pattern ) );
                painter->drawRect( (int)(first.x * (double)xScale), (int)(first.y * (double)yScale),
                                   (int)((second.x - first.x) * (double)xScale), (int)((second.y - first.y) * (double)yScale) );
            }
            else
            {
                // draw a polyline that connects the constructed points
                painter->setPen( QPen( m_engineColor, 2 ) );
                for ( int i = 1; i < points.count(); ++i )
                    painter->drawLine( (int)(points[i - 1].x * (double)xScale), (int)(points[i - 1].y * (double)yScale),
                                       (int)(points[i].x * (double)xScale), (int)(points[i].y * (double)yScale) );
                painter->drawLine( (int)(points.last().x * (double)xScale), (int)(points.last().y * (double)yScale),
                                   (int)(movingpoint.x * (double)xScale), (int)(movingpoint.y * (double)yScale) );
            }
        }

        QList< Okular::Annotation* > end()
        {
            m_creationCompleted = false;

            // find out annotation's description node
            if ( m_annotElement.isNull() )
                return QList< Okular::Annotation* >();

            // find out annotation's type
            Okular::Annotation * ann = 0;
            QString typeString = m_annotElement.attribute( "type" );

            // create LineAnnotation from path
            if ( typeString == "Line" || typeString == "Polyline" || typeString == "Polygon" )
            {
                if ( points.count() < 2 )
                    return QList< Okular::Annotation* >();

                //add note
                Okular::LineAnnotation * la = new Okular::LineAnnotation();
                ann = la;
                QLinkedList<Okular::NormalizedPoint> list;
                for ( int i = 0; i < points.count(); ++i )
                    list.append( points[ i ] );

                la->setLinePoints( list );

                if ( numofpoints == -1 )
                    la->setLineClosed( true );

                la->setBoundingRectangle( normRect );

            }

            // safety check
            if ( !ann )
                return QList< Okular::Annotation* >();

            // set common attributes
            ann->style().setColor( m_annotElement.hasAttribute( "color" ) ?
                    m_annotElement.attribute( "color" ) : m_engineColor );
            if ( m_annotElement.hasAttribute( "opacity" ) )
                ann->style().setOpacity( m_annotElement.attribute( "opacity", "1.0" ).toDouble() );
            // return annotation

            return QList< Okular::Annotation* >() << ann;
        }

    private:
        QList<Okular::NormalizedPoint> points;
        Okular::NormalizedPoint newPoint;
        Okular::NormalizedPoint movingpoint;
        QRect rect;
        QRect movingrect;
        Okular::NormalizedRect normRect;
        bool m_block;
        bool last;
        int numofpoints;
};

/** @short TextSelectorEngine */
class TextSelectorEngine : public AnnotatorEngine
{
    public:
        TextSelectorEngine( const QDomElement & engineElement, PageView * pageView )
            : AnnotatorEngine( engineElement ), m_pageView( pageView ),
            selection( 0 )
        {
            // parse engine specific attributes
        }

        ~TextSelectorEngine()
        {
            delete selection;
        }

        QRect event( EventType type, Button button, double nX, double nY, double xScale, double yScale, const Okular::Page * /*page*/ )
        {
            // only proceed if pressing left button
            if ( button != Left )
                return QRect();

            if ( type == Press )
            {
                lastPoint.x = nX;
                lastPoint.y = nY;
                QRect oldrect = rect;
                rect = QRect();
                return oldrect;
            }
            else if ( type == Move )
            {
                if ( item() )
                {
                    QPoint start( (int)( lastPoint.x * item()->uncroppedWidth() ), (int)( lastPoint.y * item()->uncroppedHeight() ) );
                    QPoint end( (int)( nX * item()->uncroppedWidth() ), (int)( nY * item()->uncroppedHeight() ) );
                    delete selection;
                    selection = 0;
                    Okular::RegularAreaRect * newselection = m_pageView->textSelectionForItem( item(), start, end );
                    if ( !newselection->isEmpty() )
                    {
                        QList<QRect> geom = newselection->geometry( (int)xScale, (int)yScale );
                        QRect newrect;
                        Q_FOREACH ( const QRect& r, geom )
                        {
                            if ( newrect.isNull() )
                                newrect = r;
                            else
                                newrect |= r;
                        }
                        rect |= newrect;
                        selection = newselection;
                    }
                    else
                    {
                        delete newselection;
                    }
                }
            }
            else if ( type == Release && selection )
            {
                m_creationCompleted = true;
            }
            return rect;
        }

        void paint( QPainter * painter, double xScale, double yScale, const QRect & /*clipRect*/ )
        {
            if ( selection )
            {
                painter->setPen( Qt::NoPen );
                QColor col = m_engineColor;
                col.setAlphaF( 0.5 );
                painter->setBrush( col );
                foreach( const Okular::NormalizedRect & r, *selection )
                {
                    painter->drawRect( r.geometry( (int)xScale, (int)yScale ) );
                }
            }
        }

        QList< Okular::Annotation* > end()
        {
            m_creationCompleted = false;

            // safety checks
            if ( m_annotElement.isNull() || !selection )
                return QList< Okular::Annotation* >();

            // find out annotation's type
            Okular::Annotation * ann = 0;
            QString typeString = m_annotElement.attribute( "type" );

            Okular::HighlightAnnotation::HighlightType type = Okular::HighlightAnnotation::Highlight;
            bool typevalid = false;
            // create HighlightAnnotation's from the selected area
            if ( typeString == "Highlight" )
            {
                type = Okular::HighlightAnnotation::Highlight;
                typevalid = true;
            }
            else if ( typeString == "Squiggly" )
            {
                type = Okular::HighlightAnnotation::Squiggly;
                typevalid = true;
            }
            else if ( typeString == "Underline" )
            {
                type = Okular::HighlightAnnotation::Underline;
                typevalid = true;
            }
            else if ( typeString == "StrikeOut" )
            {
                type = Okular::HighlightAnnotation::StrikeOut;
                typevalid = true;
            }
            if ( typevalid )
            {
                Okular::HighlightAnnotation * ha = new Okular::HighlightAnnotation();
                ha->setHighlightType( type );
                ha->setBoundingRectangle( Okular::NormalizedRect( rect, item()->uncroppedWidth(), item()->uncroppedHeight() ) );
                foreach ( const Okular::NormalizedRect & r, *selection )
                {
                    Okular::HighlightAnnotation::Quad q;
                    q.setCapStart( false );
                    q.setCapEnd( false );
                    q.setFeather( 1.0 );
                    q.setPoint( Okular::NormalizedPoint( r.left, r.bottom ), 0 );
                    q.setPoint( Okular::NormalizedPoint( r.right, r.bottom ), 1 );
                    q.setPoint( Okular::NormalizedPoint( r.right, r.top ), 2 );
                    q.setPoint( Okular::NormalizedPoint( r.left, r.top ), 3 );
                    ha->highlightQuads().append( q );
                }
                ann = ha;
            }

            delete selection;
            selection = 0;

            // safety check
            if ( !ann )
                return QList< Okular::Annotation* >();

            // set common attributes
            ann->style().setColor( m_annotElement.hasAttribute( "color" ) ?
                m_annotElement.attribute( "color" ) : m_engineColor );
            if ( m_annotElement.hasAttribute( "opacity" ) )
                ann->style().setOpacity( m_annotElement.attribute( "opacity", "1.0" ).toDouble() );

            // return annotations
            return QList< Okular::Annotation* >() << ann;
        }

    private:
        // data
        PageView * m_pageView;
        // TODO: support more pages
        Okular::RegularAreaRect * selection;
        Okular::NormalizedPoint lastPoint;
        QRect rect;
};


/** PageViewAnnotator **/

PageViewAnnotator::PageViewAnnotator( PageView * parent, Okular::Document * storage )
    : QObject( parent ), m_document( storage ), m_pageView( parent ),
    m_toolBar( 0 ), m_engine( 0 ), m_textToolsEnabled( false ), m_toolsEnabled( false ),
    m_lastToolID( -1 ), m_lockedItem( 0 )
{
    // load the tools from the 'xml tools definition' file. store the tree internally.
    QFile infoFile( KStandardDirs::locate("data", "okular/tools.xml") );
    if ( infoFile.exists() && infoFile.open( QIODevice::ReadOnly ) )
    {
        QDomDocument doc( "annotatingTools" );
        if ( doc.setContent( &infoFile ) )
        {
            m_toolsDefinition = doc.elementsByTagName("annotatingTools").item( 0 ).toElement();

            // create the AnnotationToolItems from the XML dom tree
            QDomNode toolDescription = m_toolsDefinition.firstChild();
            while ( toolDescription.isElement() )
            {
                QDomElement toolElement = toolDescription.toElement();
                if ( toolElement.tagName() == "tool" )
                {
                    AnnotationToolItem item;
                    item.id = toolElement.attribute("id").toInt();
                    item.text = i18n( toolElement.attribute( "name" ).toUtf8() );
                    item.pixmap = toolElement.attribute("pixmap");
                    QDomNode shortcutNode = toolElement.elementsByTagName( "shortcut" ).item( 0 );
                    if ( shortcutNode.isElement() )
                        item.shortcut = shortcutNode.toElement().text();
                    QDomNodeList engineNodeList = toolElement.elementsByTagName( "engine" );
                    if ( engineNodeList.size() > 0 )
                    {
                        QDomElement engineEl = engineNodeList.item( 0 ).toElement();
                        if ( !engineEl.isNull() && engineEl.hasAttribute( "type" ) )
                            item.isText = engineEl.attribute( "type" ) == QLatin1String( "TextSelector" );
                    }
                    m_items.push_back( item );
                }
                toolDescription = toolDescription.nextSibling();
            }
        }
        else
            kWarning() << "AnnotatingTools XML file seems to be damaged";
        infoFile.close();
    }
    else
        kWarning() << "Unable to open AnnotatingTools XML definition";
}

PageViewAnnotator::~PageViewAnnotator()
{
    delete m_engine;
}

void PageViewAnnotator::setEnabled( bool on )
{
    if ( !on )
    {
        // remove toolBar
        if ( m_toolBar )
            m_toolBar->hideAndDestroy();
        m_toolBar = 0;
        // deactivate the active tool, if any
        slotToolSelected( -1 );
        return;
    }

    // if no tools are defined, don't show the toolbar
    if ( !m_toolsDefinition.hasChildNodes() )
        return;

    // create toolBar
    if ( !m_toolBar )
    {
        m_toolBar = new PageViewToolBar( m_pageView, m_pageView->viewport() );
        m_toolBar->setSide( (PageViewToolBar::Side)Okular::Settings::editToolBarPlacement() );
        m_toolBar->setItems( m_items );
        m_toolBar->setToolsEnabled( m_toolsEnabled );
        m_toolBar->setTextToolsEnabled( m_textToolsEnabled );
        connect( m_toolBar, SIGNAL( toolSelected(int) ),
                this, SLOT( slotToolSelected(int) ) );
        connect( m_toolBar, SIGNAL( orientationChanged(int) ),
                this, SLOT( slotSaveToolbarOrientation(int) ) );
    }

    // show the toolBar
    m_toolBar->showAndAnimate();

    // ask for Author's name if not already set
    if ( Okular::Settings::identityAuthor().isEmpty() )
    {
        // get default username from the kdelibs/kdecore/KUser
        KUser currentUser;
        QString userName = currentUser.property( KUser::FullName ).toString();
        // ask the user for confirmation/change
        bool firstTry = true;
        while ( firstTry || userName.isEmpty()  )
        {
            QString prompt = firstTry ? i18n( "Please insert your name or initials:" ) :
                i18n( "You must set this name:" );
            userName = KInputDialog::getText( i18n("Annotations author"), prompt, userName );
            firstTry = false;
        }
        // save the name
        Okular::Settings::setIdentityAuthor( userName );
        Okular::Settings::self()->writeConfig();
    }
}

void PageViewAnnotator::setTextToolsEnabled( bool enabled )
{
    m_textToolsEnabled = enabled;
    if ( m_toolBar )
        m_toolBar->setTextToolsEnabled( m_textToolsEnabled );
}

void PageViewAnnotator::setToolsEnabled( bool enabled )
{
    m_toolsEnabled = enabled;
    if ( m_toolBar )
        m_toolBar->setToolsEnabled( m_toolsEnabled );
}

bool PageViewAnnotator::routeEvents() const
{
    return m_engine && m_toolBar;
}

QRect PageViewAnnotator::routeEvent( QMouseEvent * e, PageViewItem * item )
{
    if ( !item ) return QRect();

    // find out mouse event type
    AnnotatorEngine::EventType eventType = AnnotatorEngine::Press;
    if ( e->type() == QEvent::MouseMove )
        eventType = AnnotatorEngine::Move;
    else if ( e->type() == QEvent::MouseButtonRelease )
        eventType = AnnotatorEngine::Release;

    // find out the pressed button
    AnnotatorEngine::Button button = AnnotatorEngine::None;
    Qt::MouseButtons buttonState = ( eventType == AnnotatorEngine::Move ) ? e->buttons() : e->button();
    if ( buttonState == Qt::LeftButton )
        button = AnnotatorEngine::Left;
    else if ( buttonState == Qt::RightButton )
        button = AnnotatorEngine::Right;

    // find out normalized mouse coords inside current item
    const QRect & itemRect = item->uncroppedGeometry();
    double nX = item->absToPageX(e->x());
    double nY = item->absToPageY(e->y());

    QRect modifiedRect;

    // 1. lock engine to current item
    if ( m_lockedItem && item != m_lockedItem )
        return QRect();
    if ( !m_lockedItem && eventType == AnnotatorEngine::Press )
    {
        m_lockedItem = item;
        m_engine->setItem( m_lockedItem );
    }

    // 2. use engine to perform operations
    QRect paintRect = m_engine->event( eventType, button, nX, nY, itemRect.width(), itemRect.height(), item->page() );

    // 3. update absolute extents rect and send paint event(s)
    if ( paintRect.isValid() )
    {
        // 3.1. unite old and new painting regions
        QRegion compoundRegion( m_lastDrawnRect );
        m_lastDrawnRect = paintRect;
        m_lastDrawnRect.translate( itemRect.left(), itemRect.top() );
        // 3.2. decompose paint region in rects and send paint events
        QVector<QRect> rects = compoundRegion.unite( m_lastDrawnRect ).rects();
        for ( int i = 0; i < rects.count(); i++ )
            m_pageView->widget()->update( rects[i] );
        modifiedRect = compoundRegion.boundingRect() | m_lastDrawnRect;
    }

    // 4. if engine has finished, apply Annotation to the page
    if ( m_engine->creationCompleted() )
    {
        // apply engine data to the Annotation's and reset engine
        QList< Okular::Annotation* > annotations = m_engine->end();
        // attach the newly filled annotations to the page
        foreach ( Okular::Annotation * annotation, annotations )
        {
            if ( !annotation ) continue;

            annotation->setCreationDate( QDateTime::currentDateTime() );
            annotation->setModificationDate( QDateTime::currentDateTime() );
            annotation->setAuthor( Okular::Settings::identityAuthor() );
            m_document->addPageAnnotation( m_lockedItem->pageNumber(), annotation );
        }

        // go on creating annotations of the same type
        // for now, disable the "construct again the same annotation"
        //slotToolSelected( m_lastToolID );
        slotToolSelected( -1 );
        m_toolBar->selectButton( -1 );
    }

    return modifiedRect;
}

bool PageViewAnnotator::routeKeyEvent( QKeyEvent * event )
{
    if ( event->key() == Qt::Key_Escape )
    {
        m_toolBar->selectButton( -1 );
        return true;
    }
    return false;
}

bool PageViewAnnotator::routePaints( const QRect & wantedRect ) const
{
    return m_engine && m_toolBar && wantedRect.intersects( m_lastDrawnRect ) && m_lockedItem;
}

void PageViewAnnotator::routePaint( QPainter * painter, const QRect & paintRect )
{
    // if there's no locked item, then there's no decided place to draw on
    if ( !m_lockedItem )
        return;

#ifndef NDEBUG
    // [DEBUG] draw the paint region if enabled
    if ( Okular::Settings::debugDrawAnnotationRect() )
        painter->drawRect( paintRect );
#endif
    // move painter to current itemGeometry rect
    const QRect & itemRect = m_lockedItem->uncroppedGeometry();
    painter->save();
    painter->translate( itemRect.topLeft() );
    // TODO: Clip annotation painting to cropped page.

    // transform cliprect from absolute to item relative coords
    QRect annotRect = paintRect.intersect( m_lastDrawnRect );
    annotRect.translate( -itemRect.topLeft() );

    // use current engine for painting (in virtual page coordinates)
    m_engine->paint( painter, m_lockedItem->uncroppedWidth(), m_lockedItem->uncroppedHeight(), annotRect );
    painter->restore();
}

void PageViewAnnotator::slotToolSelected( int toolID )
{
    // terminate any previous operation
    if ( m_engine )
    {
        delete m_engine;
        m_engine = 0;
    }
    m_lockedItem = 0;
    if ( m_lastDrawnRect.isValid() )
    {
        m_pageView->widget()->update( m_lastDrawnRect );
        m_lastDrawnRect = QRect();
    }

    // store current tool for later usage
    m_lastToolID = toolID;

    // handle tool deselection
    if ( toolID == -1 )
    {
        m_pageView->displayMessage( QString() );
        return;
    }

    // for the selected tool create the Engine
    QDomNode toolNode = m_toolsDefinition.firstChild();
    while ( toolNode.isElement() )
    {
        QDomElement toolElement = toolNode.toElement();
        toolNode = toolNode.nextSibling();

        // only find out the element describing selected tool
        if ( toolElement.tagName() != "tool" || toolElement.attribute("id").toInt() != toolID )
            continue;

        // parse tool properties
        QDomNode toolSubNode = toolElement.firstChild();
        while ( toolSubNode.isElement() )
        {
            QDomElement toolSubElement = toolSubNode.toElement();
            toolSubNode = toolSubNode.nextSibling();

            // create the AnnotatorEngine
            if ( toolSubElement.tagName() == "engine" )
            {
                QString type = toolSubElement.attribute( "type" );
                if ( type == "SmoothLine" )
                    m_engine = new SmoothPathEngine( toolSubElement );
                else if ( type == "PickPoint" )
                    m_engine = new PickPointEngine( toolSubElement );
                else if ( type == "PolyLine" )
                    m_engine = new PolyLineEngine( toolSubElement );
                else if ( type == "TextSelector" )
                    m_engine = new TextSelectorEngine( toolSubElement, m_pageView );
                else
                    kWarning().nospace() << "tools.xml: engine type:'" << type << "' is not defined!";
            }
            // display the tooltip
            else if ( toolSubElement.tagName() == "tooltip" )
            {
                QString tip = toolSubElement.text();
                if ( !tip.isEmpty() )
                    m_pageView->displayMessage( i18nc( "Annotation tool", tip.toUtf8() ), PageViewMessage::Annotation );
            }
        }

        // consistancy warning
        if ( !m_engine )
            kWarning() << "tools.xml: couldn't find good engine description. check xml.";

        // stop after parsing selected tool's node
        break;
    }
}

void PageViewAnnotator::slotSaveToolbarOrientation( int side )
{
    Okular::Settings::setEditToolBarPlacement( (int)side );
    Okular::Settings::self()->writeConfig();
}

#include "pageviewannotator.moc"
