/***************************************************************************
 *   Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it>             *
 *   Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org>          *
 *                                                                         *
 *   With portions of code from kpdf/kpdf_pagewidget.cc by:                *
 *     Copyright (C) 2002 by Wilco Greven <greven@kde.org>                 *
 *     Copyright (C) 2003 by Christophe Devriese                           *
 *                           <Christophe.Devriese@student.kuleuven.ac.be>  *
 *     Copyright (C) 2003 by Laurent Montel <montel@kde.org>               *
 *     Copyright (C) 2003 by Dirk Mueller <mueller@kde.org>                *
 *     Copyright (C) 2004 by James Ots <kde@jamesots.com>                  *
 *     Copyright (C) 2011 by Jiri Baum - NICTA <jiri@baum.com.au>          *
 *                                                                         *
 *   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 "pageview.h"

// qt/kde includes
#include <qcursor.h>
#include <qevent.h>
#include <qimage.h>
#include <qpainter.h>
#include <qtimer.h>
#include <qset.h>
#include <qscrollbar.h>
#include <qtooltip.h>
#include <qapplication.h>
#include <qclipboard.h>

#include <kaction.h>
#include <kactionmenu.h>
#include <kstandardaction.h>
#include <kactioncollection.h>
#include <kmenu.h>
#include <klocale.h>
#include <kfiledialog.h>
#include <kglobal.h>
#include <kglobalsettings.h>
#include <kinputdialog.h>
#include <kselectaction.h>
#include <ktoggleaction.h>
#include <kdebug.h>
#include <kdeversion.h>
#include <kmessagebox.h>
#include <kicon.h>
#include <kurifilter.h>
#include <kstringhandler.h>
#include <ktoolinvocation.h>
#include <krun.h>

// system includes
#include <math.h>
#include <stdlib.h>

// local includes
#include "formwidgets.h"
#include "pageviewutils.h"
#include "pagepainter.h"
#include "core/annotations.h"
#include "annotwindow.h"
#include "guiutils.h"
#include "annotationpopup.h"
#include "pageviewannotator.h"
#include "priorities.h"
#include "toolaction.h"
#include "tts.h"
#include "videowidget.h"
#include "core/action.h"
#include "core/area.h"
#include "core/document_p.h"
#include "core/form.h"
#include "core/page.h"
#include "core/misc.h"
#include "core/generator.h"
#include "core/movie.h"
#include "core/audioplayer.h"
#include "core/sourcereference.h"
#include "core/tile.h"
#include "settings.h"
#include "settings_core.h"
#include "url_utils.h"
#include "magnifierview.h"

static int pageflags = PagePainter::Accessibility | PagePainter::EnhanceLinks |
                       PagePainter::EnhanceImages | PagePainter::Highlights |
                       PagePainter::TextSelection | PagePainter::Annotations;

static const float kZoomValues[] = { 0.12, 0.25, 0.33, 0.50, 0.66, 0.75, 1.00, 1.25, 1.50, 2.00, 4.00, 8.00, 16.00 };

static inline double normClamp( double value, double def )
{
    return ( value < 0.0 || value > 1.0 ) ? def : value;
}

struct TableSelectionPart {
    PageViewItem * item;
    Okular::NormalizedRect rectInItem;
    Okular::NormalizedRect rectInSelection;

    TableSelectionPart(PageViewItem * item_p, const Okular::NormalizedRect &rectInItem_p, const Okular::NormalizedRect &rectInSelection_p);
};

TableSelectionPart::TableSelectionPart(  PageViewItem * item_p, const Okular::NormalizedRect &rectInItem_p, const Okular::NormalizedRect &rectInSelection_p)
    : item ( item_p ), rectInItem (rectInItem_p), rectInSelection (rectInSelection_p)
{
}

// structure used internally by PageView for data storage
class PageViewPrivate
{
public:
    PageViewPrivate( PageView *qq );

    FormWidgetsController* formWidgetsController();
    OkularTTS* tts();
    QString selectedText() const;

    // the document, pageviewItems and the 'visible cache'
    PageView *q;
    Okular::Document * document;
    QVector< PageViewItem * > items;
    QLinkedList< PageViewItem * > visibleItems;
    MagnifierView *magnifierView;

    // view layout (columns and continuous in Settings), zoom and mouse
    PageView::ZoomMode zoomMode;
    float zoomFactor;
    QPoint mouseGrabPos;
    QPoint mousePressPos;
    QPoint mouseSelectPos;
    int mouseMidLastY;
    bool mouseSelecting;
    QRect mouseSelectionRect;
    QColor mouseSelectionColor;
    bool mouseTextSelecting;
    QSet< int > pagesWithTextSelection;
    bool mouseOnRect;
    Okular::Annotation * mouseAnn;
    QPoint mouseAnnPos;
    int mouseAnnPageNum;

    // table selection
    QList<double> tableSelectionCols;
    QList<double> tableSelectionRows;
    QList<TableSelectionPart> tableSelectionParts;
    bool tableDividersGuessed;

    // viewport move
    bool viewportMoveActive;
    QTime viewportMoveTime;
    QPoint viewportMoveDest;
    int lastSourceLocationViewportPageNumber;
    double lastSourceLocationViewportNormalizedX;
    double lastSourceLocationViewportNormalizedY;
    QTimer * viewportMoveTimer;
    // auto scroll
    int scrollIncrement;
    QTimer * autoScrollTimer;
    // annotations
    PageViewAnnotator * annotator;
    //text annotation dialogs list
    QHash< Okular::Annotation *, AnnotWindow * > m_annowindows;
    // other stuff
    QTimer * delayResizeEventTimer;
    bool dirtyLayout;
    bool blockViewport;                 // prevents changes to viewport
    bool blockPixmapsRequest;           // prevent pixmap requests
    PageViewMessage * messageWindow;    // in pageviewutils.h
    bool m_formsVisible;
    FormWidgetsController *formsWidgetController;
    OkularTTS * m_tts;
    QTimer * refreshTimer;
    int refreshPage;

    // infinite resizing loop prevention
    bool verticalScrollBarVisible;
    bool horizontalScrollBarVisible;

    // drag scroll
    QPoint dragScrollVector;
    QTimer dragScrollTimer;

    // left click depress
    QTimer leftClickTimer;

    // actions
    KAction * aRotateClockwise;
    KAction * aRotateCounterClockwise;
    KAction * aRotateOriginal;
    KSelectAction * aPageSizes;
    KToggleAction * aTrimMargins;
    KAction * aMouseNormal;
    KAction * aMouseSelect;
    KAction * aMouseTextSelect;
    KAction * aMouseTableSelect;
    KAction * aMouseMagnifier;
    KToggleAction * aToggleAnnotator;
    KSelectAction * aZoom;
    KAction * aZoomIn;
    KAction * aZoomOut;
    KToggleAction * aZoomFitWidth;
    KToggleAction * aZoomFitPage;
    KToggleAction * aZoomAutoFit;
    KActionMenu * aViewMode;
    KToggleAction * aViewContinuous;
    QAction * aPrevAction;
    KAction * aToggleForms;
    KAction * aSpeakDoc;
    KAction * aSpeakPage;
    KAction * aSpeakStop;
    KActionCollection * actionCollection;
    QActionGroup * mouseModeActionGroup;

    int setting_viewCols;

    // Keep track of whether tablet pen is currently pressed down
    bool penDown;
};

PageViewPrivate::PageViewPrivate( PageView *qq )
    : q( qq )
{
}

FormWidgetsController* PageViewPrivate::formWidgetsController()
{
    if ( !formsWidgetController )
    {
        formsWidgetController = new FormWidgetsController( document );
        QObject::connect( formsWidgetController, SIGNAL( changed( int ) ),
                          q, SLOT( slotFormChanged( int ) ) );
        QObject::connect( formsWidgetController, SIGNAL( action( Okular::Action* ) ),
                          q, SLOT( slotAction( Okular::Action* ) ) );
    }

    return formsWidgetController;
}

OkularTTS* PageViewPrivate::tts()
{
    if ( !m_tts )
    {
        m_tts = new OkularTTS( q );
        if ( aSpeakStop )
        {
            QObject::connect( m_tts, SIGNAL(hasSpeechs(bool)),
                              aSpeakStop, SLOT(setEnabled(bool)) );
            QObject::connect( m_tts, SIGNAL(errorMessage(QString)),
                              q, SLOT(errorMessage(QString)) );
        }
    }

    return m_tts;
}


/* PageView. What's in this file? -> quick overview.
 * Code weight (in rows) and meaning:
 *  160 - constructor and creating actions plus their connected slots (empty stuff)
 *  70  - DocumentObserver inherited methodes (important)
 *  550 - events: mouse, keyboard, drag
 *  170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes
 *  100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc..
 *  other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable,
 * and many insignificant stuff like this comment :-)
 */
PageView::PageView( QWidget *parent, Okular::Document *document )
    : QAbstractScrollArea( parent )
    , Okular::View( QString::fromLatin1( "PageView" ) )
{
    // create and initialize private storage structure
    d = new PageViewPrivate( this );
    d->document = document;
    d->aRotateClockwise = 0;
    d->aRotateCounterClockwise = 0;
    d->aRotateOriginal = 0;
    d->aViewMode = 0;
    d->zoomMode = PageView::ZoomFitWidth;
    d->zoomFactor = 1.0;
    d->mouseSelecting = false;
    d->mouseTextSelecting = false;
    d->mouseOnRect = false;
    d->mouseAnn = 0;
    d->tableDividersGuessed = false;
    d->viewportMoveActive = false;
    d->lastSourceLocationViewportPageNumber = -1;
    d->lastSourceLocationViewportNormalizedX = 0.0;
    d->lastSourceLocationViewportNormalizedY = 0.0;
    d->viewportMoveTimer = 0;
    d->scrollIncrement = 0;
    d->autoScrollTimer = 0;
    d->annotator = 0;
    d->dirtyLayout = false;
    d->blockViewport = false;
    d->blockPixmapsRequest = false;
    d->messageWindow = new PageViewMessage(this);
    d->m_formsVisible = false;
    d->formsWidgetController = 0;
    d->m_tts = 0;
    d->refreshTimer = 0;
    d->refreshPage = -1;
    d->aRotateClockwise = 0;
    d->aRotateCounterClockwise = 0;
    d->aRotateOriginal = 0;
    d->aPageSizes = 0;
    d->aTrimMargins = 0;
    d->aMouseNormal = 0;
    d->aMouseSelect = 0;
    d->aMouseTextSelect = 0;
    d->aToggleAnnotator = 0;
    d->aZoomFitWidth = 0;
    d->aZoomFitPage = 0;
    d->aZoomAutoFit = 0;
    d->aViewMode = 0;
    d->aViewContinuous = 0;
    d->aPrevAction = 0;
    d->aToggleForms = 0;
    d->aSpeakDoc = 0;
    d->aSpeakPage = 0;
    d->aSpeakStop = 0;
    d->actionCollection = 0;
    d->aPageSizes=0;
    d->setting_viewCols = Okular::Settings::viewColumns();
    d->mouseModeActionGroup = 0;
    d->penDown = false;
    d->aMouseMagnifier = 0;

    switch( Okular::Settings::zoomMode() )
    {
        case 0:
        {
            d->zoomFactor = 1;
            d->zoomMode = PageView::ZoomFixed;
            break;
        }
        case 1:
        {
            d->zoomMode = PageView::ZoomFitWidth;
            break;
        }
        case 2:
        {
            d->zoomMode = PageView::ZoomFitPage;
            break;
        }
        case 3:
        {
            d->zoomMode = PageView::ZoomFitAuto;
            break;
        }
    }

    d->delayResizeEventTimer = new QTimer( this );
    d->delayResizeEventTimer->setSingleShot( true );
    connect( d->delayResizeEventTimer, SIGNAL(timeout()), this, SLOT(delayedResizeEvent()) );

    setFrameStyle(QFrame::NoFrame);

    setAttribute( Qt::WA_StaticContents );

    setObjectName( QLatin1String( "okular::pageView" ) );

    // viewport setup: setup focus, and track mouse
    viewport()->setFocusProxy( this );
    viewport()->setFocusPolicy( Qt::StrongFocus );
    viewport()->setAttribute( Qt::WA_OpaquePaintEvent );
    viewport()->setAttribute( Qt::WA_NoSystemBackground );
    viewport()->setMouseTracking( true );
    viewport()->setAutoFillBackground( false );
    // the apparently "magic" value of 20 is the same used internally in QScrollArea
    verticalScrollBar()->setSingleStep( 20 );
    horizontalScrollBar()->setSingleStep( 20 );

    // conntect the padding of the viewport to pixmaps requests
    connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRequestVisiblePixmaps(int)));
    connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRequestVisiblePixmaps(int)));
    connect( &d->dragScrollTimer, SIGNAL(timeout()), this, SLOT(slotDragScroll()) );

    d->leftClickTimer.setSingleShot( true );
    connect( &d->leftClickTimer, SIGNAL(timeout()), this, SLOT(slotShowSizeAllCursor()) );

    // set a corner button to resize the view to the page size
//    QPushButton * resizeButton = new QPushButton( viewport() );
//    resizeButton->setPixmap( SmallIcon("crop") );
//    setCornerWidget( resizeButton );
//    resizeButton->setEnabled( false );
    // connect(...);
    setAttribute( Qt::WA_InputMethodEnabled, true );

    d->magnifierView = new MagnifierView(document, this);
    d->magnifierView->hide();
    d->magnifierView->setGeometry(0, 0, 351, 201); // TODO: more dynamic?
    document->addObserver(d->magnifierView);

    connect(document, SIGNAL(processMovieAction(const Okular::MovieAction*)), this, SLOT(slotProcessMovieAction(const Okular::MovieAction*)));
    connect(document, SIGNAL(processRenditionAction(const Okular::RenditionAction*)), this, SLOT(slotProcessRenditionAction(const Okular::RenditionAction*)));

    // schedule the welcome message
    QMetaObject::invokeMethod(this, "slotShowWelcome", Qt::QueuedConnection);
}

PageView::~PageView()
{
    if ( d->m_tts )
        d->m_tts->stopAllSpeechs();

    // delete the local storage structure

    // We need to assign it to a different list otherwise slotAnnotationWindowDestroyed
    // will bite us and clear d->m_annowindows
    QHash< Okular::Annotation *, AnnotWindow * > annowindows = d->m_annowindows;
    d->m_annowindows.clear();
    qDeleteAll( annowindows );

    // delete all widgets
    QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd();
    for ( ; dIt != dEnd; ++dIt )
        delete *dIt;
    delete d->formsWidgetController;
    d->document->removeObserver( this );
    delete d;
}

void PageView::setupBaseActions( KActionCollection * ac )
{
    d->actionCollection = ac;

    // Zoom actions ( higher scales takes lots of memory! )
    d->aZoom  = new KSelectAction(KIcon( "page-zoom" ), i18n("Zoom"), this);
    ac->addAction("zoom_to", d->aZoom );
    d->aZoom->setEditable( true );
    d->aZoom->setMaxComboViewCount( 14 );
    connect( d->aZoom, SIGNAL(triggered(QAction*)), this, SLOT(slotZoom()) );
    updateZoomText();

    d->aZoomIn = KStandardAction::zoomIn( this, SLOT(slotZoomIn()), ac );

    d->aZoomOut = KStandardAction::zoomOut( this, SLOT(slotZoomOut()), ac );
}

void PageView::setupViewerActions( KActionCollection * ac )
{
    d->actionCollection = ac;

    d->aZoomIn->setShortcut( QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Plus) );
    d->aZoomOut->setShortcut( QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Minus) );

    // orientation menu actions
    d->aRotateClockwise = new KAction( KIcon( "object-rotate-right" ), i18n( "Rotate &Right" ), this );
    d->aRotateClockwise->setIconText( i18nc( "Rotate right", "Right" ) );
    ac->addAction( "view_orientation_rotate_cw", d->aRotateClockwise );
    d->aRotateClockwise->setEnabled( false );
    connect( d->aRotateClockwise, SIGNAL(triggered()), this, SLOT(slotRotateClockwise()) );
    d->aRotateCounterClockwise = new KAction( KIcon( "object-rotate-left" ), i18n( "Rotate &Left" ), this );
    d->aRotateCounterClockwise->setIconText( i18nc( "Rotate left", "Left" ) );
    ac->addAction( "view_orientation_rotate_ccw", d->aRotateCounterClockwise );
    d->aRotateCounterClockwise->setEnabled( false );
    connect( d->aRotateCounterClockwise, SIGNAL(triggered()), this, SLOT(slotRotateCounterClockwise()) );
    d->aRotateOriginal = new KAction( i18n( "Original Orientation" ), this );
    ac->addAction( "view_orientation_original", d->aRotateOriginal );
    d->aRotateOriginal->setEnabled( false );
    connect( d->aRotateOriginal, SIGNAL(triggered()), this, SLOT(slotRotateOriginal()) );

    d->aPageSizes = new KSelectAction(i18n("&Page Size"), this);
    ac->addAction("view_pagesizes", d->aPageSizes);
    d->aPageSizes->setEnabled( false );

    connect( d->aPageSizes , SIGNAL(triggered(int)),
         this, SLOT(slotPageSizes(int)) );

    d->aTrimMargins  = new KToggleAction( i18n( "&Trim Margins" ), this );
    ac->addAction("view_trim_margins", d->aTrimMargins );
    connect( d->aTrimMargins, SIGNAL(toggled(bool)), SLOT(slotTrimMarginsToggled(bool)) );
    d->aTrimMargins->setChecked( Okular::Settings::trimMargins() );

    d->aZoomFitWidth  = new KToggleAction(KIcon( "zoom-fit-width" ), i18n("Fit &Width"), this);
    ac->addAction("view_fit_to_width", d->aZoomFitWidth );
    connect( d->aZoomFitWidth, SIGNAL(toggled(bool)), SLOT(slotFitToWidthToggled(bool)) );

    d->aZoomFitPage  = new KToggleAction(KIcon( "zoom-fit-best" ), i18n("Fit &Page"), this);
    ac->addAction("view_fit_to_page", d->aZoomFitPage );
    connect( d->aZoomFitPage, SIGNAL(toggled(bool)), SLOT(slotFitToPageToggled(bool)) );

    d->aZoomAutoFit  = new KToggleAction(KIcon( "zoom-fit-best" ), i18n("&Auto Fit"), this);
    ac->addAction("view_auto_fit", d->aZoomAutoFit );
    connect( d->aZoomAutoFit, SIGNAL(toggled(bool)), SLOT(slotAutoFitToggled(bool)) );

    // View-Layout actions
    d->aViewMode = new KActionMenu( KIcon( "view-split-left-right" ), i18n( "&View Mode" ), this );
    d->aViewMode->setDelayed( false );
#define ADD_VIEWMODE_ACTION( text, name, id ) \
do { \
    KAction *vm = new KAction( text, d->aViewMode->menu() ); \
    vm->setCheckable( true ); \
    vm->setData( qVariantFromValue( id ) ); \
    d->aViewMode->addAction( vm ); \
    ac->addAction( name, vm ); \
    vmGroup->addAction( vm ); \
} while( 0 )
    ac->addAction("view_render_mode", d->aViewMode );
    QActionGroup *vmGroup = new QActionGroup( d->aViewMode->menu() );
    ADD_VIEWMODE_ACTION( i18n( "Single Page" ), "view_render_mode_single", (int)Okular::Settings::EnumViewMode::Single );
    ADD_VIEWMODE_ACTION( i18n( "Facing Pages" ), "view_render_mode_facing", (int)Okular::Settings::EnumViewMode::Facing );
    ADD_VIEWMODE_ACTION( i18n( "Facing Pages (Center First Page)" ), "view_render_mode_facing_center_first", (int)Okular::Settings::EnumViewMode::FacingFirstCentered );
    ADD_VIEWMODE_ACTION( i18n( "Overview" ), "view_render_mode_overview", (int)Okular::Settings::EnumViewMode::Summary );
    const QList<QAction *> viewModeActions = d->aViewMode->menu()->actions();
    foreach(QAction *viewModeAction, viewModeActions)
    {
        if (viewModeAction->data().toInt() == Okular::Settings::viewMode())
        {
            viewModeAction->setChecked( true );
        }
    }
    connect( vmGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotViewMode(QAction*)) );
#undef ADD_VIEWMODE_ACTION

    d->aViewContinuous  = new KToggleAction(KIcon( "view-list-text" ), i18n("&Continuous"), this);
    ac->addAction("view_continuous", d->aViewContinuous );
    connect( d->aViewContinuous, SIGNAL(toggled(bool)), SLOT(slotContinuousToggled(bool)) );
    d->aViewContinuous->setChecked( Okular::Settings::viewContinuous() );

    // Mouse mode actions for viewer mode
    d->mouseModeActionGroup = new QActionGroup( this );
    d->mouseModeActionGroup->setExclusive( true );
    d->aMouseNormal  = new KAction( KIcon( "input-mouse" ), i18n( "&Browse Tool" ), this );
    ac->addAction("mouse_drag", d->aMouseNormal );
    connect( d->aMouseNormal, SIGNAL(triggered()), this, SLOT(slotSetMouseNormal()) );
    d->aMouseNormal->setIconText( i18nc( "Browse Tool", "Browse" ) );
    d->aMouseNormal->setCheckable( true );
    d->aMouseNormal->setShortcut( Qt::CTRL + Qt::Key_1 );
    d->aMouseNormal->setActionGroup( d->mouseModeActionGroup );
    d->aMouseNormal->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Browse );

    KAction * mz  = new KAction(KIcon( "page-zoom" ), i18n("&Zoom Tool"), this);
    ac->addAction("mouse_zoom", mz );
    connect( mz, SIGNAL(triggered()), this, SLOT(slotSetMouseZoom()) );
    mz->setIconText( i18nc( "Zoom Tool", "Zoom" ) );
    mz->setCheckable( true );
    mz->setShortcut( Qt::CTRL + Qt::Key_2 );
    mz->setActionGroup( d->mouseModeActionGroup );
    mz->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Zoom );

    KAction * aToggleChangeColors  = new KAction(i18n("&Toggle Change Colors"), this);
    ac->addAction("toggle_change_colors", aToggleChangeColors );
    connect( aToggleChangeColors, SIGNAL(triggered()), this, SLOT(slotToggleChangeColors()) );
}

// WARNING: 'setupViewerActions' must have been called before this method
void PageView::setupActions( KActionCollection * ac )
{
    d->actionCollection = ac;

    d->aZoomIn->setShortcut( KStandardShortcut::zoomIn() );
    d->aZoomOut->setShortcut( KStandardShortcut::zoomOut() );

    // Mouse-Mode actions
    d->aMouseSelect  = new KAction(KIcon( "select-rectangular" ), i18n("&Selection Tool"), this);
    ac->addAction("mouse_select", d->aMouseSelect );
    connect( d->aMouseSelect, SIGNAL(triggered()), this, SLOT(slotSetMouseSelect()) );
    d->aMouseSelect->setIconText( i18nc( "Select Tool", "Selection" ) );
    d->aMouseSelect->setCheckable( true );
    d->aMouseSelect->setShortcut( Qt::CTRL + Qt::Key_3 );
    d->aMouseSelect->setActionGroup( d->mouseModeActionGroup );
    d->aMouseSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::RectSelect );

    d->aMouseTextSelect  = new KAction(KIcon( "draw-text" ), i18n("&Text Selection Tool"), this);
    ac->addAction("mouse_textselect", d->aMouseTextSelect );
    connect( d->aMouseTextSelect, SIGNAL(triggered()), this, SLOT(slotSetMouseTextSelect()) );
    d->aMouseTextSelect->setIconText( i18nc( "Text Selection Tool", "Text Selection" ) );
    d->aMouseTextSelect->setCheckable( true );
    d->aMouseTextSelect->setShortcut( Qt::CTRL + Qt::Key_4 );
    d->aMouseTextSelect->setActionGroup( d->mouseModeActionGroup );
    d->aMouseTextSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TextSelect );

    d->aMouseTableSelect  = new KAction(KIcon( "table" ), i18n("T&able Selection Tool"), this);
    ac->addAction("mouse_tableselect", d->aMouseTableSelect );
    connect( d->aMouseTableSelect, SIGNAL( triggered() ), this, SLOT( slotSetMouseTableSelect() ) );
    d->aMouseTableSelect->setIconText( i18nc( "Table Selection Tool", "Table Selection" ) );
    d->aMouseTableSelect->setCheckable( true );
    d->aMouseTableSelect->setShortcut( Qt::CTRL + Qt::Key_5 );
    d->aMouseTableSelect->setActionGroup( d->mouseModeActionGroup );
    d->aMouseTableSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TableSelect );

    d->aMouseMagnifier = new KAction(KIcon( "document-preview" ), i18n("&Magnifier"), this);
    ac->addAction("mouse_magnifier", d->aMouseMagnifier );
    connect( d->aMouseMagnifier, SIGNAL(triggered()), this, SLOT(slotSetMouseMagnifier()) );
    d->aMouseMagnifier->setIconText( i18nc( "Magnifier Tool", "Magnifier" ) );
    d->aMouseMagnifier->setCheckable( true );
    d->aMouseMagnifier->setShortcut( Qt::CTRL + Qt::Key_6 );
    d->aMouseMagnifier->setActionGroup( d->mouseModeActionGroup );
    d->aMouseMagnifier->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Magnifier );

    d->aToggleAnnotator  = new KToggleAction(KIcon( "draw-freehand" ), i18n("&Review"), this);
    ac->addAction("mouse_toggle_annotate", d->aToggleAnnotator );
    d->aToggleAnnotator->setCheckable( true );
    connect( d->aToggleAnnotator, SIGNAL(toggled(bool)), SLOT(slotToggleAnnotator(bool)) );
    d->aToggleAnnotator->setShortcut( Qt::Key_F6 );

    ToolAction *ta = new ToolAction( this );
    ac->addAction( "mouse_selecttools", ta );
    ta->addAction( d->aMouseSelect );
    ta->addAction( d->aMouseTextSelect );
    ta->addAction( d->aMouseTableSelect );

    // speak actions
    d->aSpeakDoc = new KAction( KIcon( "text-speak" ), i18n( "Speak Whole Document" ), this );
    ac->addAction( "speak_document", d->aSpeakDoc );
    d->aSpeakDoc->setEnabled( false );
    connect( d->aSpeakDoc, SIGNAL(triggered()), SLOT(slotSpeakDocument()) );

    d->aSpeakPage = new KAction( KIcon( "text-speak" ), i18n( "Speak Current Page" ), this );
    ac->addAction( "speak_current_page", d->aSpeakPage );
    d->aSpeakPage->setEnabled( false );
    connect( d->aSpeakPage, SIGNAL(triggered()), SLOT(slotSpeakCurrentPage()) );

    d->aSpeakStop = new KAction( KIcon( "media-playback-stop" ), i18n( "Stop Speaking" ), this );
    ac->addAction( "speak_stop_all", d->aSpeakStop );
    d->aSpeakStop->setEnabled( false );
    connect( d->aSpeakStop, SIGNAL(triggered()), SLOT(slotStopSpeaks()) );

    // Other actions
    KAction * su  = new KAction(i18n("Scroll Up"), this);
    ac->addAction("view_scroll_up", su );
    connect( su, SIGNAL(triggered()), this, SLOT(slotAutoScrollUp()) );
    su->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_Up) );
    addAction(su);

    KAction * sd  = new KAction(i18n("Scroll Down"), this);
    ac->addAction("view_scroll_down", sd );
    connect( sd, SIGNAL(triggered()), this, SLOT(slotAutoScrollDown()) );
    sd->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_Down) );
    addAction(sd);

    KAction * spu = new KAction(i18n("Scroll Page Up"), this);
    ac->addAction( "view_scroll_page_up", spu );
    connect( spu, SIGNAL(triggered()), this, SLOT(slotScrollUp()) );
    spu->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_Space) );
    addAction( spu );

    KAction * spd = new KAction(i18n("Scroll Page Down"), this);
    ac->addAction( "view_scroll_page_down", spd );
    connect( spd, SIGNAL(triggered()), this, SLOT(slotScrollDown()) );
    spd->setShortcut( QKeySequence(Qt::Key_Space) );
    addAction( spd );

    d->aToggleForms = new KAction( this );
    ac->addAction( "view_toggle_forms", d->aToggleForms );
    connect( d->aToggleForms, SIGNAL(triggered()), this, SLOT(slotToggleForms()) );
    d->aToggleForms->setEnabled( false );
    toggleFormWidgets( false );

    // Setup undo and redo actions
    KAction *kundo = KStandardAction::create( KStandardAction::Undo, d->document, SLOT(undo()), ac );
    KAction *kredo = KStandardAction::create( KStandardAction::Redo, d->document, SLOT(redo()), ac );
    connect(d->document, SIGNAL(canUndoChanged(bool)), kundo, SLOT(setEnabled(bool)));
    connect(d->document, SIGNAL(canRedoChanged(bool)), kredo, SLOT(setEnabled(bool)));
    kundo->setEnabled(false);
    kredo->setEnabled(false);
}

bool PageView::canFitPageWidth() const
{
    return Okular::Settings::viewMode() != Okular::Settings::EnumViewMode::Single || d->zoomMode != ZoomFitWidth;
}

void PageView::fitPageWidth( int page )
{
    // zoom: Fit Width, columns: 1. setActions + relayout + setPage + update
    d->zoomMode = ZoomFitWidth;
    Okular::Settings::setViewMode( 0 );
    d->aZoomFitWidth->setChecked( true );
    d->aZoomFitPage->setChecked( false );
    d->aZoomAutoFit->setChecked( false );
    d->aViewMode->menu()->actions().at( 0 )->setChecked( true );
    viewport()->setUpdatesEnabled( false );
    slotRelayoutPages();
    viewport()->setUpdatesEnabled( true );
    d->document->setViewportPage( page );
    updateZoomText();
    setFocus();
}

void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNumber )
{
    if ( !annotation )
        return;

    // find the annot window
    AnnotWindow* existWindow = 0;
    QHash< Okular::Annotation *, AnnotWindow * >::ConstIterator it = d->m_annowindows.constFind( annotation );
    if ( it != d->m_annowindows.constEnd() )
    {
        existWindow = *it;
    }

    if ( existWindow == 0 )
    {
        existWindow = new AnnotWindow( this, annotation, d->document, pageNumber );
        connect(existWindow, SIGNAL(destroyed(QObject*)), this, SLOT(slotAnnotationWindowDestroyed(QObject*)));

        d->m_annowindows.insert( annotation, existWindow );
    }

    existWindow->show();
}

void PageView::slotAnnotationWindowDestroyed( QObject * window )
{
    QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin();
    QHash< Okular::Annotation*, AnnotWindow * >::Iterator itEnd = d->m_annowindows.end();
    while ( it != itEnd )
    {
        if ( it.value() == window )
        {
            it = d->m_annowindows.erase( it );
        }
        else
        {
            ++it;
        }
    }
}

void PageView::displayMessage( const QString & message, const QString & details, PageViewMessage::Icon icon, int duration )
{
    if ( !Okular::Settings::showOSD() )
    {
        if (icon == PageViewMessage::Error)
        {
            if ( !details.isEmpty() )
                KMessageBox::detailedError( this, message, details );
            else
                KMessageBox::error( this, message );
        }
        return;
    }

    // hide messageWindow if string is empty
    if ( message.isEmpty() )
        return d->messageWindow->hide();

    // display message (duration is length dependant)
    if (duration==-1)
    {
        duration = 500 + 100 * message.length();
        if ( !details.isEmpty() )
            duration += 500 + 100 * details.length();
    }
    d->messageWindow->display( message, details, icon, duration );
}

void PageView::reparseConfig()
{
    // set the scroll bars policies
    Qt::ScrollBarPolicy scrollBarMode = Okular::Settings::showScrollBars() ?
        Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff;
    if ( horizontalScrollBarPolicy() != scrollBarMode )
    {
        setHorizontalScrollBarPolicy( scrollBarMode );
        setVerticalScrollBarPolicy( scrollBarMode );
    }

    if ( Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Summary &&
         ( (int)Okular::Settings::viewColumns() != d->setting_viewCols ) )
    {
        d->setting_viewCols = Okular::Settings::viewColumns();

        slotRelayoutPages();
    }

    updatePageStep();

    if ( d->annotator )
    {
        d->annotator->setEnabled( false );
        d->annotator->reparseConfig();
        if ( d->aToggleAnnotator->isChecked() )
            slotToggleAnnotator( true );
    }

    // Something like invert colors may have changed
    // As we don't have a way to find out the old value
    // We just update the viewport, this shouldn't be that bad
    // since it's just a repaint of pixmaps we already have
    viewport()->update();
}

KActionCollection *PageView::actionCollection() const
{
    return d->actionCollection;
}

KAction *PageView::toggleFormsAction() const
{
    return d->aToggleForms;
}

int PageView::contentAreaWidth() const
{
    return horizontalScrollBar()->maximum() + viewport()->width();
}

int PageView::contentAreaHeight() const
{
    return verticalScrollBar()->maximum() + viewport()->height();
}

QPoint PageView::contentAreaPosition() const
{
    return QPoint( horizontalScrollBar()->value(), verticalScrollBar()->value() );
}

QPoint PageView::contentAreaPoint( const QPoint & pos ) const
{
    return pos + contentAreaPosition();
}

QPointF PageView::contentAreaPoint( const QPointF & pos ) const
{
    return pos + contentAreaPosition();
}

QString PageViewPrivate::selectedText() const
{
    if ( pagesWithTextSelection.isEmpty() )
        return QString();

    QString text;
    QList< int > selpages = pagesWithTextSelection.toList();
    qSort( selpages );
    const Okular::Page * pg = 0;
    if ( selpages.count() == 1 )
    {
        pg = document->page( selpages.first() );
        text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) );
    }
    else
    {
        pg = document->page( selpages.first() );
        text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) );
        int end = selpages.count() - 1;
        for( int i = 1; i < end; ++i )
        {
            pg = document->page( selpages.at( i ) );
            text.append( pg->text( 0, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) );
        }
        pg = document->page( selpages.last() );
        text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) );
    }
    return text;
}

void PageView::copyTextSelection() const
{
    const QString text = d->selectedText();
    if ( !text.isEmpty() )
    {
        QClipboard *cb = QApplication::clipboard();
        cb->setText( text, QClipboard::Clipboard );
    }
}

void PageView::selectAll()
{
    QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd();
    for ( ; it < itEnd; ++it )
    {
        Okular::RegularAreaRect * area = textSelectionForItem( *it );
        d->pagesWithTextSelection.insert( (*it)->pageNumber() );
        d->document->setPageTextSelection( (*it)->pageNumber(), area, palette().color( QPalette::Active, QPalette::Highlight ) );
    }
}

//BEGIN DocumentObserver inherited methods
void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags )
{
    bool documentChanged = setupFlags & Okular::DocumentObserver::DocumentChanged;
    // reuse current pages if nothing new
    if ( ( pageSet.count() == d->items.count() ) && !documentChanged && !( setupFlags & Okular::DocumentObserver::NewLayoutForPages ) )
    {
        int count = pageSet.count();
        for ( int i = 0; (i < count) && !documentChanged; i++ )
            if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() )
                documentChanged = true;
        if ( !documentChanged )
            return;
    }

    // delete all widgets (one for each page in pageSet)
    QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd();
    for ( ; dIt != dEnd; ++dIt )
        delete *dIt;
    d->items.clear();
    d->visibleItems.clear();
    d->pagesWithTextSelection.clear();
    toggleFormWidgets( false );
    if ( d->formsWidgetController )
        d->formsWidgetController->dropRadioButtons();

    bool haspages = !pageSet.isEmpty();
    bool hasformwidgets = false;
    // create children widgets
    QVector< Okular::Page * >::const_iterator setIt = pageSet.constBegin(), setEnd = pageSet.constEnd();
    for ( ; setIt != setEnd; ++setIt )
    {
        PageViewItem * item = new PageViewItem( *setIt );
        d->items.push_back( item );
#ifdef PAGEVIEW_DEBUG
        kDebug().nospace() << "cropped geom for " << d->items.last()->pageNumber() << " is " << d->items.last()->croppedGeometry();
#endif
        const QLinkedList< Okular::FormField * > pageFields = (*setIt)->formFields();
        QLinkedList< Okular::FormField * >::const_iterator ffIt = pageFields.constBegin(), ffEnd = pageFields.constEnd();
        for ( ; ffIt != ffEnd; ++ffIt )
        {
            Okular::FormField * ff = *ffIt;
            FormWidgetIface * w = FormWidgetFactory::createWidget( ff, viewport() );
            if ( w )
            {
                w->setPageItem( item );
                w->setFormWidgetsController( d->formWidgetsController() );
                w->setVisibility( false );
                w->setCanBeFilled( d->document->isAllowed( Okular::AllowFillForms ) );
                item->formWidgets().insert( ff->id(), w );
                hasformwidgets = true;
            }
        }
        const QLinkedList< Okular::Annotation * > annotations = (*setIt)->annotations();
        QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.constBegin(), aEnd = annotations.constEnd();
        for ( ; aIt != aEnd; ++aIt )
        {
            Okular::Annotation * a = *aIt;
            if ( a->subType() == Okular::Annotation::AMovie )
            {
                Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a );
                VideoWidget * vw = new VideoWidget( movieAnn, movieAnn->movie(), d->document, viewport() );
                item->videoWidgets().insert( movieAnn->movie(), vw );
                vw->pageInitialized();
            }
            else if ( a->subType() == Okular::Annotation::AScreen )
            {
                const Okular::ScreenAnnotation * screenAnn = static_cast< Okular::ScreenAnnotation * >( a );
                Okular::Movie *movie = GuiUtils::renditionMovieFromScreenAnnotation( screenAnn );
                if ( movie )
                {
                    VideoWidget * vw = new VideoWidget( screenAnn, movie, d->document, viewport() );
                    item->videoWidgets().insert( movie, vw );
                    vw->pageInitialized();
                }
            }
        }
    }

    // invalidate layout so relayout/repaint will happen on next viewport change
    if ( haspages )
    {
        // We do a delayed call to slotRelayoutPages but also set the dirtyLayout
        // because we might end up in notifyViewportChanged while slotRelayoutPages
        // has not been done and we don't want that to happen
        d->dirtyLayout = true;
        QMetaObject::invokeMethod(this, "slotRelayoutPages", Qt::QueuedConnection);
    }
    else
    {
        // update the mouse cursor when closing because we may have close through a link and
        // want the cursor to come back to the normal cursor
        updateCursor();
        // then, make the message window and scrollbars disappear, and trigger a repaint
        d->messageWindow->hide();
        resizeContentArea( QSize( 0,0 ) );
        viewport()->update(); // when there is no change to the scrollbars, no repaint would
                              // be done and the old document would still be shown
    }

    // OSD to display pages
    if ( documentChanged && pageSet.count() > 0 && Okular::Settings::showOSD() )
        d->messageWindow->display(
            i18np(" Loaded a one-page document.",
                 " Loaded a %1-page document.",
                 pageSet.count() ),
            QString(),
            PageViewMessage::Info, 4000 );

    updateActionState( haspages, documentChanged, hasformwidgets );

    // We need to assign it to a different list otherwise slotAnnotationWindowDestroyed
    // will bite us and clear d->m_annowindows
    QHash< Okular::Annotation *, AnnotWindow * > annowindows = d->m_annowindows;
    d->m_annowindows.clear();
    qDeleteAll( annowindows );

    selectionClear();
}

void PageView::updateActionState( bool haspages, bool documentChanged, bool hasformwidgets )
{
    if ( d->aPageSizes )
    { // may be null if dummy mode is on
        bool pageSizes = d->document->supportsPageSizes();
        d->aPageSizes->setEnabled( pageSizes );
        // set the new page sizes:
        // - if the generator supports them
        // - if the document changed
        if ( pageSizes && documentChanged )
        {
            QStringList items;
            foreach ( const Okular::PageSize &p, d->document->pageSizes() )
                items.append( p.name() );
            d->aPageSizes->setItems( items );
        }
    }

    if ( d->aTrimMargins )
        d->aTrimMargins->setEnabled( haspages );

    if ( d->aViewMode )
        d->aViewMode->setEnabled( haspages );

    if ( d->aViewContinuous )
        d->aViewContinuous->setEnabled( haspages );

    if ( d->aZoomFitWidth )
        d->aZoomFitWidth->setEnabled( haspages );
    if ( d->aZoomFitPage )
        d->aZoomFitPage->setEnabled( haspages );
    if ( d->aZoomAutoFit )
        d->aZoomAutoFit->setEnabled( haspages );

    if ( d->aZoom )
    {
        d->aZoom->selectableActionGroup()->setEnabled( haspages );
        d->aZoom->setEnabled( haspages );
    }
    if ( d->aZoomIn )
        d->aZoomIn->setEnabled( haspages );
    if ( d->aZoomOut )
        d->aZoomOut->setEnabled( haspages );

    if ( d->mouseModeActionGroup )
        d->mouseModeActionGroup->setEnabled( haspages );

    if ( d->aRotateClockwise )
        d->aRotateClockwise->setEnabled( haspages );
    if ( d->aRotateCounterClockwise )
        d->aRotateCounterClockwise->setEnabled( haspages );
    if ( d->aRotateOriginal )
        d->aRotateOriginal->setEnabled( haspages );
    if ( d->aToggleForms )
    { // may be null if dummy mode is on
        d->aToggleForms->setEnabled( haspages && hasformwidgets );
    }
    bool allowAnnotations = d->document->isAllowed( Okular::AllowNotes );
    if ( d->annotator )
    {
        bool allowTools = haspages && allowAnnotations;
        d->annotator->setToolsEnabled( allowTools );
        d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() );
    }
    if ( d->aToggleAnnotator )
    {
        if ( !allowAnnotations && d->aToggleAnnotator->isChecked() )
        {
            d->aToggleAnnotator->trigger();
        }
        d->aToggleAnnotator->setEnabled( allowAnnotations );
    }
    if ( d->aSpeakDoc )
    {
        const bool enablettsactions = haspages ? Okular::Settings::useKTTSD() : false;
        d->aSpeakDoc->setEnabled( enablettsactions );
        d->aSpeakPage->setEnabled( enablettsactions );
    }
    if (d->aMouseMagnifier)
        d->aMouseMagnifier->setEnabled(d->document->supportsTiles());
}

bool PageView::areSourceLocationsShownGraphically() const
{
    return Okular::Settings::showSourceLocationsGraphically();
}

void PageView::setShowSourceLocationsGraphically(bool show)
{
    if( show == Okular::Settings::showSourceLocationsGraphically() )
    {
        return;
    }
    Okular::Settings::setShowSourceLocationsGraphically( show );
    viewport()->update();
}

void PageView::setLastSourceLocationViewport( const Okular::DocumentViewport& vp )
{
    if( vp.rePos.enabled )
    {
        d->lastSourceLocationViewportNormalizedX = normClamp( vp.rePos.normalizedX, 0.5 );
        d->lastSourceLocationViewportNormalizedY = normClamp( vp.rePos.normalizedY, 0.0 );
    }
    else
    {
        d->lastSourceLocationViewportNormalizedX = 0.5;
        d->lastSourceLocationViewportNormalizedY = 0.0;
    }
    d->lastSourceLocationViewportPageNumber = vp.pageNumber;
    viewport()->update();
}

void PageView::clearLastSourceLocationViewport()
{
    d->lastSourceLocationViewportPageNumber = -1;
    d->lastSourceLocationViewportNormalizedX = 0.0;
    d->lastSourceLocationViewportNormalizedY = 0.0;
    viewport()->update();
}

void PageView::notifyViewportChanged( bool smoothMove )
{
    QMetaObject::invokeMethod(this, "slotRealNotifyViewportChanged", Qt::QueuedConnection, Q_ARG( bool, smoothMove ));
}

void PageView::slotRealNotifyViewportChanged( bool smoothMove )
{
    // if we are the one changing viewport, skip this nofity
    if ( d->blockViewport )
        return;

    // block setViewport outgoing calls
    d->blockViewport = true;

    // find PageViewItem matching the viewport description
    const Okular::DocumentViewport & vp = d->document->viewport();
    PageViewItem * item = 0;
    QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd();
    for ( ; iIt != iEnd; ++iIt )
        if ( (*iIt)->pageNumber() == vp.pageNumber )
        {
            item = *iIt;
            break;
        }
    if ( !item )
    {
        kWarning() << "viewport for page" << vp.pageNumber << "has no matching item!";
        d->blockViewport = false;
        return;
    }
#ifdef PAGEVIEW_DEBUG
    kDebug() << "document viewport changed";
#endif
    // relayout in "Single Pages" mode or if a relayout is pending
    d->blockPixmapsRequest = true;
    if ( !Okular::Settings::viewContinuous() || d->dirtyLayout )
        slotRelayoutPages();

    // restore viewport center or use default {x-center,v-top} alignment
    const QRect & r = item->croppedGeometry();
    int newCenterX = r.left(),
        newCenterY = r.top();
    if ( vp.rePos.enabled )
    {
        if ( vp.rePos.pos == Okular::DocumentViewport::Center )
        {
            newCenterX += (int)( normClamp( vp.rePos.normalizedX, 0.5 ) * (double)r.width() );
            newCenterY += (int)( normClamp( vp.rePos.normalizedY, 0.0 ) * (double)r.height() );
        }
        else
        {
            // TopLeft
            newCenterX += (int)( normClamp( vp.rePos.normalizedX, 0.0 ) * (double)r.width() + viewport()->width() / 2 );
            newCenterY += (int)( normClamp( vp.rePos.normalizedY, 0.0 ) * (double)r.height() + viewport()->height() / 2 );
        }
    }
    else
    {
        newCenterX += r.width() / 2;
        newCenterY += viewport()->height() / 2 - 10;
    }

    // if smooth movement requested, setup parameters and start it
    if ( smoothMove )
    {
        d->viewportMoveActive = true;
        d->viewportMoveTime.start();
        d->viewportMoveDest.setX( newCenterX );
        d->viewportMoveDest.setY( newCenterY );
        if ( !d->viewportMoveTimer )
        {
            d->viewportMoveTimer = new QTimer( this );
            connect( d->viewportMoveTimer, SIGNAL(timeout()),
                     this, SLOT(slotMoveViewport()) );
        }
        d->viewportMoveTimer->start( 25 );
        verticalScrollBar()->setEnabled( false );
        horizontalScrollBar()->setEnabled( false );
    }
    else
        center( newCenterX, newCenterY );
    d->blockPixmapsRequest = false;

    // request visible pixmaps in the current viewport and recompute it
    slotRequestVisiblePixmaps();

    // enable setViewport calls
    d->blockViewport = false;

    if( viewport() )
    {
        viewport()->update();
    }

    // since the page has moved below cursor, update it
    updateCursor();
}

void PageView::notifyPageChanged( int pageNumber, int changedFlags )
{
    // only handle pixmap / highlight changes notifies
    if ( changedFlags & DocumentObserver::Bookmark )
        return;

    if ( changedFlags & DocumentObserver::Annotations )
    {
        const QLinkedList< Okular::Annotation * > annots = d->document->page( pageNumber )->annotations();
        const QLinkedList< Okular::Annotation * >::ConstIterator annItEnd = annots.end();
        QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin();
        for ( ; it != d->m_annowindows.end(); )
        {
            QLinkedList< Okular::Annotation * >::ConstIterator annIt = qFind( annots, it.key() );
            if ( annIt != annItEnd )
            {
                (*it)->reloadInfo();
                ++it;
            }
            else
            {
                AnnotWindow *w = *it;
                it = d->m_annowindows.erase( it );
                // Need to delete after removing from the list
                // otherwise deleting will call slotAnnotationWindowDestroyed which will mess
                // the list and the iterators
                delete w;
            }
        }
    }

    if ( changedFlags & DocumentObserver::BoundingBox )
    {
#ifdef PAGEVIEW_DEBUG
        kDebug() << "BoundingBox change on page" << pageNumber;
#endif
        slotRelayoutPages();
        slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already!
        // Repaint the whole widget since layout may have changed
        viewport()->update();
        return;
    }

    // iterate over visible items: if page(pageNumber) is one of them, repaint it
    QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd();
    for ( ; iIt != iEnd; ++iIt )
        if ( (*iIt)->pageNumber() == pageNumber && (*iIt)->isVisible() )
        {
            // update item's rectangle plus the little outline
            QRect expandedRect = (*iIt)->croppedGeometry();
            // a PageViewItem is placed in the global page layout,
            // while we need to map its position in the viewport coordinates
            // (to get the correct area to repaint)
            expandedRect.translate( -contentAreaPosition() );
            expandedRect.adjust( -1, -1, 3, 3 );
            viewport()->update( expandedRect );

            // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor
            if ( cursor().shape() != Qt::SizeVerCursor )
            {
                // since the page has been regenerated below cursor, update it
                updateCursor();
            }
            break;
        }
}

void PageView::notifyContentsCleared( int changedFlags )
{
    // if pixmaps were cleared, re-ask them
    if ( changedFlags & DocumentObserver::Pixmap )
        QMetaObject::invokeMethod(this, "slotRequestVisiblePixmaps", Qt::QueuedConnection);
}

void PageView::notifyZoom( int factor )
{
    if ( factor > 0 )
        updateZoom( ZoomIn );
    else
        updateZoom( ZoomOut );
}

bool PageView::canUnloadPixmap( int pageNumber ) const
{
    if ( Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low ||
         Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal )
    {
        // if the item is visible, forbid unloading
        QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd();
        for ( ; vIt != vEnd; ++vIt )
            if ( (*vIt)->pageNumber() == pageNumber )
                return false;
    }
    else
    {
        // forbid unloading of the visible items, and of the previous and next
        QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd();
        for ( ; vIt != vEnd; ++vIt )
            if ( abs( (*vIt)->pageNumber() - pageNumber ) <= 1 )
                return false;
    }
    // if hidden premit unloading
    return true;
}

void PageView::notifyCurrentPageChanged( int previous, int current )
{
    if ( previous != -1 )
    {
        PageViewItem * item = d->items.at( previous );
        if ( item )
        {
            Q_FOREACH ( VideoWidget *videoWidget, item->videoWidgets() )
                videoWidget->pageLeft();
        }
    }

    if ( current != -1 )
    {
        PageViewItem * item = d->items.at( current );
        if ( item )
        {
            Q_FOREACH ( VideoWidget *videoWidget, item->videoWidgets() )
                videoWidget->pageEntered();
        }

        // update zoom text and factor if in a ZoomFit/* zoom mode
        if ( d->zoomMode != ZoomFixed )
            updateZoomText();
    }
}

//END DocumentObserver inherited methods

//BEGIN View inherited methods
bool PageView::supportsCapability( ViewCapability capability ) const
{
    switch ( capability )
    {
        case Zoom:
        case ZoomModality:
            return true;
    }
    return false;
}

Okular::View::CapabilityFlags PageView::capabilityFlags( ViewCapability capability ) const
{
    switch ( capability )
    {
        case Zoom:
        case ZoomModality:
            return CapabilityRead | CapabilityWrite | CapabilitySerializable;
    }
    return 0;
}

QVariant PageView::capability( ViewCapability capability ) const
{
    switch ( capability )
    {
        case Zoom:
            return d->zoomFactor;
        case ZoomModality:
            return d->zoomMode;
    }
    return QVariant();
}

void PageView::setCapability( ViewCapability capability, const QVariant &option )
{
    switch ( capability )
    {
        case Zoom:
        {
            bool ok = true;
            double factor = option.toDouble( &ok );
            if ( ok && factor > 0.0 )
            {
                d->zoomFactor = static_cast< float >( factor );
                updateZoom( ZoomRefreshCurrent );
            }
            break;
        }
        case ZoomModality:
        {
            bool ok = true;
            int mode = option.toInt( &ok );
            if ( ok )
            {
                if ( mode >= 0 && mode < 3 )
                    updateZoom( (ZoomMode)mode );
            }
            break;
        }
    }
}

//END View inherited methods

//BEGIN widget events
void PageView::paintEvent(QPaintEvent *pe)
{
        const QPoint areaPos = contentAreaPosition();
        // create the rect into contents from the clipped screen rect
        QRect viewportRect = viewport()->rect();
        viewportRect.translate( areaPos );
        QRect contentsRect = pe->rect().translated( areaPos ).intersect( viewportRect );
        if ( !contentsRect.isValid() )
            return;

#ifdef PAGEVIEW_DEBUG
        kDebug() << "paintevent" << contentsRect;
#endif

        // create the screen painter. a pixel painted at contentsX,contentsY
        // appears to the top-left corner of the scrollview.
        QPainter screenPainter( viewport() );
        // translate to simulate the scrolled content widget
        screenPainter.translate( -areaPos );

        // selectionRect is the normalized mouse selection rect
        QRect selectionRect = d->mouseSelectionRect;
        if ( !selectionRect.isNull() )
            selectionRect = selectionRect.normalized();
        // selectionRectInternal without the border
        QRect selectionRectInternal = selectionRect;
        selectionRectInternal.adjust( 1, 1, -1, -1 );
        // color for blending
        QColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ?
                            d->mouseSelectionColor : Qt::red;

        // subdivide region into rects
        const QVector<QRect> &allRects = pe->region().rects();
        uint numRects = allRects.count();

        // preprocess rects area to see if it worths or not using subdivision
        uint summedArea = 0;
        for ( uint i = 0; i < numRects; i++ )
        {
            const QRect & r = allRects[i];
            summedArea += r.width() * r.height();
        }
        // very elementary check: SUMj(Region[j].area) is less than boundingRect.area
        bool useSubdivision = summedArea < (0.6 * contentsRect.width() * contentsRect.height());
        if ( !useSubdivision )
            numRects = 1;

        // iterate over the rects (only one loop if not using subdivision)
        for ( uint i = 0; i < numRects; i++ )
        {
            if ( useSubdivision )
            {
                // set 'contentsRect' to a part of the sub-divided region
                contentsRect = allRects[i].translated( areaPos ).intersect( viewportRect );
                if ( !contentsRect.isValid() )
                    continue;
            }
#ifdef PAGEVIEW_DEBUG
            kDebug() << contentsRect;
#endif

            // note: this check will take care of all things requiring alpha blending (not only selection)
            bool wantCompositing = !selectionRect.isNull() && contentsRect.intersects( selectionRect );
            // also alpha-blend when there is a table selection...
            wantCompositing |= !d->tableSelectionParts.isEmpty();

            if ( wantCompositing && Okular::Settings::enableCompositing() )
            {
                // create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0})
                QPixmap doubleBuffer( contentsRect.size() );
                QPainter pixmapPainter( &doubleBuffer );
                pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() );

                // 1) Layer 0: paint items and clear bg on unpainted rects
                drawDocumentOnPainter( contentsRect, &pixmapPainter );
                // 2a) Layer 1a: paint (blend) transparent selection (rectangle)
                if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
                    !selectionRectInternal.contains( contentsRect ) )
                {
                    QRect blendRect = selectionRectInternal.intersect( contentsRect );
                    // skip rectangles covered by the selection's border
                    if ( blendRect.isValid() )
                    {
                        // grab current pixmap into a new one to colorize contents
                        QPixmap blendedPixmap( blendRect.width(), blendRect.height() );
                        QPainter p( &blendedPixmap );
                        p.drawPixmap( 0, 0, doubleBuffer,
                                    blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(),
                                    blendRect.width(), blendRect.height() );

                        QColor blCol = selBlendColor.dark( 140 );
                        blCol.setAlphaF( 0.2 );
                        p.fillRect( blendedPixmap.rect(), blCol );
                        p.end();
                        // copy the blended pixmap back to its place
                        pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap );
                    }
                    // draw border (red if the selection is too small)
                    pixmapPainter.setPen( selBlendColor );
                    pixmapPainter.drawRect( selectionRect.adjusted( 0, 0, -1, -1 ) );
                }
                // 2b) Layer 1b: paint (blend) transparent selection (table)
                foreach (const TableSelectionPart &tsp, d->tableSelectionParts) {
                    QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight());
                    selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () );
                    QRect selectionPartRectInternal = selectionPartRect;
                    selectionPartRectInternal.adjust( 1, 1, -1, -1 );
                    if ( !selectionPartRect.isNull() && selectionPartRect.intersects( contentsRect ) &&
                        !selectionPartRectInternal.contains( contentsRect ) )
                    {
                        QRect blendRect = selectionPartRectInternal.intersect( contentsRect );
                        // skip rectangles covered by the selection's border
                        if ( blendRect.isValid() )
                        {
                            // grab current pixmap into a new one to colorize contents
                            QPixmap blendedPixmap( blendRect.width(), blendRect.height() );
                            QPainter p( &blendedPixmap );
                            p.drawPixmap( 0, 0, doubleBuffer,
                                        blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(),
                                        blendRect.width(), blendRect.height() );

                            QColor blCol = d->mouseSelectionColor.dark( 140 );
                            blCol.setAlphaF( 0.2 );
                            p.fillRect( blendedPixmap.rect(), blCol );
                            p.end();
                            // copy the blended pixmap back to its place
                            pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap );
                        }
                        // draw border (red if the selection is too small)
                        pixmapPainter.setPen( d->mouseSelectionColor );
                        pixmapPainter.drawRect( selectionPartRect.adjusted( 0, 0, -1, -1 ) );
                    }
                }
                drawTableDividers( &pixmapPainter );
                // 3) Layer 1: give annotator painting control
                if ( d->annotator && d->annotator->routePaints( contentsRect ) )
                    d->annotator->routePaint( &pixmapPainter, contentsRect );
                // 4) Layer 2: overlays
                if ( Okular::Settings::debugDrawBoundaries() )
                {
                    pixmapPainter.setPen( Qt::blue );
                    pixmapPainter.drawRect( contentsRect );
                }

                // finish painting and draw contents
                pixmapPainter.end();
                screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer );
            }
            else
            {
                // 1) Layer 0: paint items and clear bg on unpainted rects
                drawDocumentOnPainter( contentsRect, &screenPainter );
                // 2a) Layer 1a: paint opaque selection (rectangle)
                if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
                    !selectionRectInternal.contains( contentsRect ) )
                {
                    screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) );
                    screenPainter.drawRect( selectionRect );
                }
                // 2b) Layer 1b: paint opaque selection (table)
                foreach (const TableSelectionPart &tsp, d->tableSelectionParts) {
                    QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight());
                    selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () );
                    QRect selectionPartRectInternal = selectionPartRect;
                    selectionPartRectInternal.adjust( 1, 1, -1, -1 );
                    if ( !selectionPartRect.isNull() && selectionPartRect.intersects( contentsRect ) &&
                        !selectionPartRectInternal.contains( contentsRect ) )
                    {
                        screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) );
                        screenPainter.drawRect( selectionPartRect );
                    }
                }
                drawTableDividers( &screenPainter );
                // 3) Layer 1: give annotator painting control
                if ( d->annotator && d->annotator->routePaints( contentsRect ) )
                    d->annotator->routePaint( &screenPainter, contentsRect );
                // 4) Layer 2: overlays
                if ( Okular::Settings::debugDrawBoundaries() )
                {
                    screenPainter.setPen( Qt::red );
                    screenPainter.drawRect( contentsRect );
                }
            }
        }
}

void PageView::drawTableDividers(QPainter * screenPainter)
{
        if (!d->tableSelectionParts.isEmpty()) {
            screenPainter->setPen( d->mouseSelectionColor.dark() );
            if (d->tableDividersGuessed) {
                QPen p = screenPainter->pen();
                p.setStyle( Qt::DashLine );
                screenPainter->setPen( p );
            }
            foreach (const TableSelectionPart &tsp, d->tableSelectionParts) {
                QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight());
                selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () );
                QRect selectionPartRectInternal = selectionPartRect;
                selectionPartRectInternal.adjust( 1, 1, -1, -1 );
                foreach(double col, d->tableSelectionCols) {
                    if (col >= tsp.rectInSelection.left && col <= tsp.rectInSelection.right) {
                        col = (col - tsp.rectInSelection.left) / (tsp.rectInSelection.right - tsp.rectInSelection.left);
                        const int x =  selectionPartRect.left() + col * selectionPartRect.width() + 0.5;
                        screenPainter->drawLine(
                            x, selectionPartRectInternal.top(),
                            x, selectionPartRectInternal.top() + selectionPartRectInternal.height()
                        );
                    }
                }
                foreach(double row, d->tableSelectionRows) {
                    if (row >= tsp.rectInSelection.top && row <= tsp.rectInSelection.bottom) {
                        row = (row - tsp.rectInSelection.top) / (tsp.rectInSelection.bottom - tsp.rectInSelection.top);
                        const int y =  selectionPartRect.top() + row * selectionPartRect.height() + 0.5;
                        screenPainter->drawLine(
                            selectionPartRectInternal.left(), y,
                            selectionPartRectInternal.left() + selectionPartRectInternal.width(), y
                        );
                    }
                }
            }
        }
}

void PageView::resizeEvent( QResizeEvent *e )
{
    if ( d->items.isEmpty() )
    {
        resizeContentArea( e->size() );
        return;
    }

    if ( ( d->zoomMode == ZoomFitWidth || d->zoomMode == ZoomFitAuto ) && !verticalScrollBar()->isVisible() && qAbs(e->oldSize().height() - e->size().height()) < verticalScrollBar()->width() && d->verticalScrollBarVisible )
    {
        // this saves us from infinite resizing loop because of scrollbars appearing and disappearing
        // see bug 160628 for more info
        // TODO looks are still a bit ugly because things are left uncentered
        // but better a bit ugly than unusable
        d->verticalScrollBarVisible = false;
        resizeContentArea( e->size() );
        return;
    }
    else if ( d->zoomMode == ZoomFitAuto && !horizontalScrollBar()->isVisible() && qAbs(e->oldSize().width() - e->size().width()) < horizontalScrollBar()->height() && d->horizontalScrollBarVisible )
    {
        // this saves us from infinite resizing loop because of scrollbars appearing and disappearing
        // TODO looks are still a bit ugly because things are left uncentered
        // but better a bit ugly than unusable
        d->horizontalScrollBarVisible = false;
        resizeContentArea( e->size() );
        return;
    }

    // start a timer that will refresh the pixmap after 0.2s
    d->delayResizeEventTimer->start( 200 );
    d->verticalScrollBarVisible = verticalScrollBar()->isVisible();
    d->horizontalScrollBarVisible = horizontalScrollBar()->isVisible();
}

void PageView::keyPressEvent( QKeyEvent * e )
{
    e->accept();

    // if performing a selection or dyn zooming, disable keys handling
    if ( ( d->mouseSelecting && e->key() != Qt::Key_Escape ) || ( QApplication::mouseButtons () & Qt::MidButton ) )
        return;

    // if viewport is moving, disable keys handling
    if ( d->viewportMoveActive )
        return;

    // move/scroll page by using keys
    switch ( e->key() )
    {
        case Qt::Key_J:
        case Qt::Key_K:
        case Qt::Key_Down:
        case Qt::Key_PageDown:
        case Qt::Key_Up:
        case Qt::Key_PageUp:
        case Qt::Key_Backspace:
            if ( e->key() == Qt::Key_Down
                 || e->key() == Qt::Key_PageDown
                 || e->key() == Qt::Key_J )
            {
                bool singleStep = e->key() == Qt::Key_Down || e->key() == Qt::Key_J;
                slotScrollDown( singleStep );
            }
            else
            {
                bool singleStep = e->key() == Qt::Key_Up || e->key() == Qt::Key_K;
                slotScrollUp( singleStep );
            }
            break;
        case Qt::Key_Left:
        case Qt::Key_H:
            if ( horizontalScrollBar()->maximum() == 0 )
            {
                //if we cannot scroll we go to the previous page vertically
                int next_page = d->document->currentPage() - viewColumns();
                d->document->setViewportPage(next_page);
            }
            else
                horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub );
            break;
        case Qt::Key_Right:
        case Qt::Key_L:
            if ( horizontalScrollBar()->maximum() == 0 )
            {
                //if we cannot scroll we advance the page vertically
                int next_page = d->document->currentPage() + viewColumns();
                d->document->setViewportPage(next_page);
            }
            else
                horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd );
            break;
        case Qt::Key_Escape:
            emit escPressed();
            selectionClear( d->tableDividersGuessed ? ClearOnlyDividers : ClearAllSelection );
            d->mousePressPos = QPoint();
            if ( d->aPrevAction )
            {
                d->aPrevAction->trigger();
                d->aPrevAction = 0;
            }
            break;
        case Qt::Key_Shift:
        case Qt::Key_Control:
            if ( d->autoScrollTimer )
            {
                if ( d->autoScrollTimer->isActive() )
                    d->autoScrollTimer->stop();
                else
                    slotAutoScoll();
                return;
            }
            // else fall trhough
        default:
            e->ignore();
            return;
    }
    // if a known key has been pressed, stop scrolling the page
    if ( d->autoScrollTimer )
    {
        d->scrollIncrement = 0;
        d->autoScrollTimer->stop();
    }
}

void PageView::keyReleaseEvent( QKeyEvent * e )
{
    e->accept();

    if ( d->annotator && d->annotator->active() )
    {
        if ( d->annotator->routeKeyEvent( e ) )
            return;
    }

    if ( e->key() == Qt::Key_Escape && d->autoScrollTimer )
    {
        d->scrollIncrement = 0;
        d->autoScrollTimer->stop();
    }
}

void PageView::inputMethodEvent( QInputMethodEvent * e )
{
    Q_UNUSED(e)
}

static QPoint rotateInRect( const QPoint &rotated, Okular::Rotation rotation )
{
    QPoint ret;

    switch ( rotation )
    {
        case Okular::Rotation90:
            ret = QPoint( rotated.y(), -rotated.x() );
            break;
        case Okular::Rotation180:
            ret = QPoint( -rotated.x(), -rotated.y() );
            break;
        case Okular::Rotation270:
            ret = QPoint( -rotated.y(), rotated.x() );
            break;
        case Okular::Rotation0:  // no modifications
        default: // other cases
            ret = rotated;
    }

    return ret;
}

void PageView::tabletEvent( QTabletEvent * e )
{
    // Ignore tablet events that we don't care about
    if ( !( e->type() == QEvent::TabletPress ||
            e->type() == QEvent::TabletRelease ||
            e->type() == QEvent::TabletMove ) )
    {
        e->ignore();
        return;
    }

    // Determine pen state
    bool penReleased = false;
    if ( e->type() == QEvent::TabletPress )
    {
        d->penDown = true;
    }
    if ( e->type() == QEvent::TabletRelease )
    {
        d->penDown = false;
        penReleased = true;
    }

    // If we're editing an annotation and the tablet pen is either down or just released
    // then dispatch event to annotator
    if ( d->annotator && d->annotator->active() && ( d->penDown || penReleased ) )
    {
        const QPoint eventPos = contentAreaPoint( e->pos() );
        PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
        const QPoint localOriginInGlobal = mapToGlobal( QPoint(0,0) );

        // routeTabletEvent will accept or ignore event as appropriate
        d->annotator->routeTabletEvent( e, pageItem, localOriginInGlobal );
    } else {
        e->ignore();
    }
}

void PageView::mouseMoveEvent( QMouseEvent * e )
{
    // don't perform any mouse action when no document is shown
    if ( d->items.isEmpty() )
        return;

    // don't perform any mouse action when viewport is autoscrolling
    if ( d->viewportMoveActive )
        return;

    // if holding mouse mid button, perform zoom
    if ( e->buttons() & Qt::MidButton )
    {
        int mouseY = e->globalPos().y();
        int deltaY = d->mouseMidLastY - mouseY;

        // wrap mouse from top to bottom
        const QRect mouseContainer = KGlobalSettings::desktopGeometry( this );
        const int absDeltaY = abs(deltaY);
        if ( absDeltaY > mouseContainer.height() / 2 )
        {
            deltaY = mouseContainer.height() - absDeltaY;
        }

        const float upperZoomLimit = d->document->supportsTiles() ? 15.99 : 3.99;
        if ( mouseY <= mouseContainer.top() + 4 &&
             d->zoomFactor < upperZoomLimit )
        {
            mouseY = mouseContainer.bottom() - 5;
            QCursor::setPos( e->globalPos().x(), mouseY );
        }
        // wrap mouse from bottom to top
        else if ( mouseY >= mouseContainer.bottom() - 4 &&
                  d->zoomFactor > 0.101 )
        {
            mouseY = mouseContainer.top() + 5;
            QCursor::setPos( e->globalPos().x(), mouseY );
        }
        // remember last position
        d->mouseMidLastY = mouseY;

        // update zoom level, perform zoom and redraw
        if ( deltaY )
        {
            d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) );
            updateZoom( ZoomRefreshCurrent );
            viewport()->repaint();
        }
        return;
    }

    const QPoint eventPos = contentAreaPoint( e->pos() );

    // if we're editing an annotation, dispatch event to it
    if ( d->annotator && d->annotator->active() )
    {
        PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
        updateCursor( eventPos );
        d->annotator->routeMouseEvent( e, pageItem );
        return;
    }

    bool leftButton = (e->buttons() == Qt::LeftButton);
    bool rightButton = (e->buttons() == Qt::RightButton);
    switch ( Okular::Settings::mouseMode() )
    {
        case Okular::Settings::EnumMouseMode::Browse:
            if ( leftButton )
            {
                d->leftClickTimer.stop();

                if ( d->mouseAnn )
                {
                    PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
                    if ( pageItem )
                    {
                        const QRect & itemRect = pageItem->uncroppedGeometry();
                        QPoint newpos = eventPos - itemRect.topLeft();
                        QPoint p( newpos - d->mouseAnnPos );
                        QPointF pf( rotateInRect( p, pageItem->page()->rotation() ) );
                        if ( pageItem->page()->rotation() % 2 == 0 )
                        {
                            pf.rx() /= pageItem->uncroppedWidth();
                            pf.ry() /= pageItem->uncroppedHeight();
                        }
                        else
                        {
                            pf.rx() /= pageItem->uncroppedHeight();
                            pf.ry() /= pageItem->uncroppedWidth();
                        }

                        d->document->translatePageAnnotation(d->mouseAnnPageNum, d->mouseAnn, Okular::NormalizedPoint( pf.x(), pf.y() ) );
                        d->mouseAnnPos = newpos;
                    }
                }
                // drag page
                else if ( !d->mouseGrabPos.isNull() )
                {
                    setCursor( Qt::ClosedHandCursor );

                    QPoint mousePos = e->globalPos();
                    QPoint delta = d->mouseGrabPos - mousePos;

                    // wrap mouse from top to bottom
                    const QRect mouseContainer = KGlobalSettings::desktopGeometry( this );
                    // If the delta is huge it probably means we just wrapped in that direction
                    const QPoint absDelta(abs(delta.x()), abs(delta.y()));
                    if ( absDelta.y() > mouseContainer.height() / 2 )
                    {
                        delta.setY(mouseContainer.height() - absDelta.y());
                    }
                    if ( absDelta.x() > mouseContainer.width() / 2 )
                    {
                        delta.setX(mouseContainer.width() - absDelta.x());
                    }
                    if ( mousePos.y() <= mouseContainer.top() + 4 &&
                         verticalScrollBar()->value() < verticalScrollBar()->maximum() - 10 )
                    {
                        mousePos.setY( mouseContainer.bottom() - 5 );
                        QCursor::setPos( mousePos );
                    }
                    // wrap mouse from bottom to top
                    else if ( mousePos.y() >= mouseContainer.bottom() - 4 &&
                              verticalScrollBar()->value() > 10 )
                    {
                        mousePos.setY( mouseContainer.top() + 5 );
                        QCursor::setPos( mousePos );
                    }
                    // remember last position
                    d->mouseGrabPos = mousePos;

                    // scroll page by position increment
                    scrollTo( horizontalScrollBar()->value() + delta.x(), verticalScrollBar()->value() + delta.y() );
                }
            }
            else if ( rightButton && !d->mousePressPos.isNull() && d->aMouseSelect )
            {
                // if mouse moves 5 px away from the press point, switch to 'selection'
                int deltaX = d->mousePressPos.x() - e->globalPos().x(),
                    deltaY = d->mousePressPos.y() - e->globalPos().y();
                if ( deltaX > 5 || deltaX < -5 || deltaY > 5 || deltaY < -5 )
                {
                    d->aPrevAction = d->aMouseNormal;
                    d->aMouseSelect->trigger();
                    QPoint newPos = eventPos + QPoint( deltaX, deltaY );
                    selectionStart( newPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false );
                    updateSelection( eventPos );
                    break;
                }
            }
            else
            {
                // only hovering the page, so update the cursor
                updateCursor();
            }
            break;

        case Okular::Settings::EnumMouseMode::Zoom:
        case Okular::Settings::EnumMouseMode::RectSelect:
        case Okular::Settings::EnumMouseMode::TableSelect:
            // set second corner of selection
            if ( d->mouseSelecting )
                updateSelection( eventPos );
            break;

        case Okular::Settings::EnumMouseMode::Magnifier:
            if ( e->buttons() ) // if any button is pressed at all
            {
                moveMagnifier( e->pos() );
                updateMagnifier( eventPos );
            }
            break;

        case Okular::Settings::EnumMouseMode::TextSelect:
            // if mouse moves 5 px away from the press point and the document soupports text extraction, do 'textselection'
            if ( !d->mouseTextSelecting && !d->mousePressPos.isNull() && d->document->supportsSearching() && ( ( eventPos - d->mouseSelectPos ).manhattanLength() > 5 ) )
            {
                d->mouseTextSelecting = true;
            }
            updateSelection( eventPos );
            updateCursor();
            break;
    }
}

void PageView::mousePressEvent( QMouseEvent * e )
{
    // don't perform any mouse action when no document is shown
    if ( d->items.isEmpty() )
        return;

    // if performing a selection or dyn zooming, disable mouse press
    if ( d->mouseSelecting || ( e->button() != Qt::MidButton && ( e->buttons() & Qt::MidButton) ) || d->viewportMoveActive )
        return;

    // if the page is scrolling, stop it
    if ( d->autoScrollTimer )
    {
        d->scrollIncrement = 0;
        d->autoScrollTimer->stop();
    }

    // if pressing mid mouse button while not doing other things, begin 'continuous zoom' mode
    if ( e->button() == Qt::MidButton )
    {
        d->mouseMidLastY = e->globalPos().y();
        setCursor( Qt::SizeVerCursor );
        return;
    }

    const QPoint eventPos = contentAreaPoint( e->pos() );

    // if we're editing an annotation, dispatch event to it
    if ( d->annotator && d->annotator->active() )
    {
        PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
        d->annotator->routeMouseEvent( e, pageItem );
        return;
    }

    // trigger history navigation for additional mouse buttons
    if ( e->button() == Qt::XButton1 )
    {
        emit mouseBackButtonClick();
        return;
    }
    if ( e->button() == Qt::XButton2 )
    {
        emit mouseForwardButtonClick();
        return;
    }

    // update press / 'start drag' mouse position
    d->mousePressPos = e->globalPos();

    // handle mode dependant mouse press actions
    bool leftButton = e->button() == Qt::LeftButton,
         rightButton = e->button() == Qt::RightButton;

//   Not sure we should erase the selection when clicking with left.
     if ( Okular::Settings::mouseMode() != Okular::Settings::EnumMouseMode::TextSelect )
       textSelectionClear();

    switch ( Okular::Settings::mouseMode() )
    {
        case Okular::Settings::EnumMouseMode::Browse:   // drag start / click / link following
            if ( leftButton )
            {
                PageViewItem * pageItem = 0;
                if ( ( e->modifiers() & Qt::ControlModifier ) && ( pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ) ) )
                {
                    // find out normalized mouse coords inside current item
                    const QRect & itemRect = pageItem->uncroppedGeometry();
                    double nX = pageItem->absToPageX(eventPos.x());
                    double nY = pageItem->absToPageY(eventPos.y());
                    const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() );
                    d->mouseAnnPos = eventPos - itemRect.topLeft();
                    if ( orect )
                        d->mouseAnn = ( (Okular::AnnotationObjectRect *)orect )->annotation();
                    // consider no annotation caught if its type is not movable
                    if ( d->mouseAnn && !d->mouseAnn->canBeMoved() )
                        d->mouseAnn = 0;
                }
                if ( d->mouseAnn )
                {
                    d->mouseAnn->setFlags( d->mouseAnn->flags() | Okular::Annotation::BeingMoved );
                    d->mouseAnnPageNum = pageItem->pageNumber();
                }
                else
                {
                    d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mousePressPos;
                    if ( !d->mouseOnRect )
                        d->leftClickTimer.start( QApplication::doubleClickInterval() + 10 );
                }
            }
            else if ( rightButton )
            {
                PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
                if ( pageItem )
                {
                    // find out normalized mouse coords inside current item
                    const QRect & itemRect = pageItem->uncroppedGeometry();
                    double nX = pageItem->absToPageX(eventPos.x());
                    double nY = pageItem->absToPageY(eventPos.y());

                    const QLinkedList< const Okular::ObjectRect *> orects = pageItem->page()->objectRects( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() );

                    if ( !orects.isEmpty() )
                    {
                        AnnotationPopup popup( d->document, AnnotationPopup::MultiAnnotationMode, this );

                        foreach ( const Okular::ObjectRect * orect, orects )
                        {
                            Okular::Annotation * ann = ( (Okular::AnnotationObjectRect *)orect )->annotation();
                            if ( ann && (ann->subType() != Okular::Annotation::AWidget) )
                                popup.addAnnotation( ann, pageItem->pageNumber() );

                        }

                        connect( &popup, SIGNAL(openAnnotationWindow(Okular::Annotation*,int)),
                                 this, SLOT(openAnnotationWindow(Okular::Annotation*,int)) );

                        popup.exec( e->globalPos() );
                    }
                }
            }
            break;

        case Okular::Settings::EnumMouseMode::Zoom:     // set first corner of the zoom rect
            if ( leftButton )
                selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ), false );
            else if ( rightButton )
                updateZoom( ZoomOut );
            break;

        case Okular::Settings::EnumMouseMode::Magnifier:
            moveMagnifier( e->pos() );
            d->magnifierView->show();
            updateMagnifier( eventPos );
            break;

        case Okular::Settings::EnumMouseMode::RectSelect:   // set first corner of the selection rect
             if ( leftButton )
             {
                selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false );
             }
            break;
        case Okular::Settings::EnumMouseMode::TableSelect:
             if ( leftButton )
             {
                if (d->tableSelectionParts.isEmpty())
                {
                    selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false );
                } else {
                    QRect updatedRect;
                    foreach (const TableSelectionPart &tsp, d->tableSelectionParts) {
                        QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight());
                        selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () );

                        // This will update the whole table rather than just the added/removed divider
                        // (which can span more than one part).
                        updatedRect = updatedRect.united(selectionPartRect);

                        if (!selectionPartRect.contains(eventPos))
                            continue;

                        // At this point it's clear we're either adding or removing a divider manually, so obviously the user is happy with the guess (if any).
                        d->tableDividersGuessed = false;

                        // There's probably a neat trick to finding which edge it's closest to,
                        // but this way has the advantage of simplicity.
                        const int fromLeft = abs(selectionPartRect.left() - eventPos.x());
                        const int fromRight = abs(selectionPartRect.left() + selectionPartRect.width() - eventPos.x());
                        const int fromTop = abs(selectionPartRect.top() - eventPos.y());
                        const int fromBottom = abs(selectionPartRect.top() + selectionPartRect.height() - eventPos.y());
                        const int colScore = fromTop<fromBottom ? fromTop : fromBottom;
                        const int rowScore = fromLeft<fromRight ? fromLeft : fromRight;

                        if (colScore < rowScore) {
                            bool deleted=false;
                            for(int i=0; i<d->tableSelectionCols.length(); i++) {
                                const double col = (d->tableSelectionCols[i] - tsp.rectInSelection.left) / (tsp.rectInSelection.right - tsp.rectInSelection.left);
                                const int colX =  selectionPartRect.left() + col * selectionPartRect.width() + 0.5;
                                if (abs(colX - eventPos.x())<=3) {
                                    d->tableSelectionCols.removeAt(i);
                                    deleted=true;

                                    break;
                                }
                            }
                            if (!deleted) {
                                double col = eventPos.x() - selectionPartRect.left();
                                col /= selectionPartRect.width(); // at this point, it's normalised within the part
                                col *= (tsp.rectInSelection.right - tsp.rectInSelection.left);
                                col += tsp.rectInSelection.left; // at this point, it's normalised within the whole table

                                d->tableSelectionCols.append(col);
                                qSort(d->tableSelectionCols);
                            }
                        } else {
                            bool deleted=false;
                            for(int i=0; i<d->tableSelectionRows.length(); i++) {
                                const double row = (d->tableSelectionRows[i] - tsp.rectInSelection.top) / (tsp.rectInSelection.bottom - tsp.rectInSelection.top);
                                const int rowY =  selectionPartRect.top() + row * selectionPartRect.height() + 0.5;
                                if (abs(rowY - eventPos.y())<=3) {
                                    d->tableSelectionRows.removeAt(i);
                                    deleted=true;

                                    break;
                                }
                            }
                            if (!deleted) {
                                double row = eventPos.y() - selectionPartRect.top();
                                row /= selectionPartRect.height(); // at this point, it's normalised within the part
                                row *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top);
                                row += tsp.rectInSelection.top; // at this point, it's normalised within the whole table

                                d->tableSelectionRows.append(row);
                                qSort(d->tableSelectionRows);
                            }
                        }
                    }
                    updatedRect.translate( -contentAreaPosition() );
                    viewport()->update( updatedRect );
                }
             }
            break;
        case Okular::Settings::EnumMouseMode::TextSelect:
            d->mouseSelectPos = eventPos;
            if ( !rightButton )
            {
                textSelectionClear();
            }
            break;
    }
}

void PageView::mouseReleaseEvent( QMouseEvent * e )
{
    // stop the drag scrolling
    d->dragScrollTimer.stop();

    d->leftClickTimer.stop();

    // don't perform any mouse action when no document is shown..
    if ( d->items.isEmpty() )
    {
        // ..except for right Clicks (emitted even it viewport is empty)
        if ( e->button() == Qt::RightButton )
            emit rightClick( 0, e->globalPos() );
        return;
    }

    // don't perform any mouse action when viewport is autoscrolling
    if ( d->viewportMoveActive )
        return;

    const QPoint eventPos = contentAreaPoint( e->pos() );

    // handle mode indepent mid buttom zoom
    if ( e->button() == Qt::MidButton )
    {
        // request pixmaps since it was disabled during drag
        slotRequestVisiblePixmaps();
        // the cursor may now be over a link.. update it
        updateCursor( eventPos );
        return;
    }

    // if we're editing an annotation, dispatch event to it
    if ( d->annotator && d->annotator->active() )
    {
        PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
        d->annotator->routeMouseEvent( e, pageItem );
        return;
    }

    if ( d->mouseAnn )
    {
        // Just finished to move the annotation
        d->mouseAnn->setFlags( d->mouseAnn->flags() & ~Okular::Annotation::BeingMoved );
        d->document->translatePageAnnotation(d->mouseAnnPageNum, d->mouseAnn, Okular::NormalizedPoint( 0.0, 0.0 ) );
        setCursor( Qt::ArrowCursor );
        d->mouseAnn = 0;
    }

    bool leftButton = e->button() == Qt::LeftButton;
    bool rightButton = e->button() == Qt::RightButton;
    switch ( Okular::Settings::mouseMode() )
    {
        case Okular::Settings::EnumMouseMode::Browse:{
            // return the cursor to its normal state after dragging
            if ( cursor().shape() == Qt::ClosedHandCursor )
                updateCursor( eventPos );

            PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
            const QPoint pressPos = contentAreaPoint( mapFromGlobal( d->mousePressPos ) );
            const PageViewItem * pageItemPressPos = pickItemOnPoint( pressPos.x(), pressPos.y() );

            // if the mouse has not moved since the press, that's a -click-
            if ( leftButton && pageItem && pageItem == pageItemPressPos &&
                 ( (d->mousePressPos - e->globalPos()).manhattanLength() < QApplication::startDragDistance() ) )
            {
                double nX = pageItem->absToPageX(eventPos.x());
                double nY = pageItem->absToPageY(eventPos.y());
                const Okular::ObjectRect * rect;
                rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
                if ( rect )
                {
                    // handle click over a link
                    const Okular::Action * action = static_cast< const Okular::Action * >( rect->object() );
                    d->document->processAction( action );
                }
                else if ( e->modifiers() == Qt::ShiftModifier )
                {
                    // TODO: find a better way to activate the source reference "links"
                    // for the moment they are activated with Shift + left click
                    // Search the nearest source reference.
                    rect = pageItem->page()->objectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
                    if ( !rect )
                    {
                        static const double s_minDistance = 0.025; // FIXME?: empirical value?
                        double distance = 0.0;
                        rect = pageItem->page()->nearestObjectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight(), &distance );
                        if ( rect && ( distance > s_minDistance ) )
                            rect = 0;
                    }
                    if ( rect )
                    {
                        const Okular::SourceReference * ref = static_cast< const Okular::SourceReference * >( rect->object() );
                        d->document->processSourceReference( ref );
                    }
                    else
                    {
                        const Okular::SourceReference * ref = d->document->dynamicSourceReference( pageItem->  pageNumber(), nX * pageItem->page()->width(), nY * pageItem->page()->height() );
                        if ( ref )
                        {
                            d->document->processSourceReference( ref );
                            delete ref;
                        }
                    }
                }
                else
                {
                    Okular::Annotation *ann = 0;

                    rect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
                    if ( rect )
                        ann = ( (Okular::AnnotationObjectRect *)rect )->annotation();

                    if ( ann )
                    {
                        if ( ann->subType() == Okular::Annotation::AMovie )
                        {
                            VideoWidget *vw = pageItem->videoWidgets().value( static_cast<Okular::MovieAnnotation*>( ann )->movie() );
                            vw->show();
                            vw->play();
                        }
                        else if ( ann->subType() == Okular::Annotation::AScreen )
                        {
                            d->document->processAction( static_cast<Okular::ScreenAnnotation*>( ann )->action() );
                        }
                    }
#if 0
                    // a link can move us to another page or even to another document, there's no point in trying to
                    //  process the click on the image once we have processes the click on the link
                    rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->width(), pageItem->height() );
                    if ( rect )
                    {
                        // handle click over a image
                    }
/*		Enrico and me have decided this is not worth the trouble it generates
                    else
                    {
                        // if not on a rect, the click selects the page
                        // if ( pageItem->pageNumber() != (int)d->document->currentPage() )
                        d->document->setViewportPage( pageItem->pageNumber(), this );
                    }*/
#endif
                }
            }
            else if ( rightButton )
            {
                if ( pageItem && pageItem == pageItemPressPos &&
                     ( (d->mousePressPos - e->globalPos()).manhattanLength() < QApplication::startDragDistance() ) )
                {
                    double nX = pageItem->absToPageX(eventPos.x());
                    double nY = pageItem->absToPageY(eventPos.y());
                    const Okular::ObjectRect * rect;
                    rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
                    if ( rect )
                    {
                        // handle right click over a link
                        const Okular::Action * link = static_cast< const Okular::Action * >( rect->object() );
                        // creating the menu and its actions
                        KMenu menu( this );
                        QAction * actProcessLink = menu.addAction( i18n( "Follow This Link" ) );
                        QAction * actStopSound = 0;
                        if ( link->actionType() == Okular::Action::Sound )
                            actStopSound = menu.addAction( i18n( "Stop Sound" ) );
                        QAction * actCopyLinkLocation = 0;
                        if ( dynamic_cast< const Okular::BrowseAction * >( link ) )
                            actCopyLinkLocation = menu.addAction( KIcon( "edit-copy" ), i18n( "Copy Link Address" ) );
                        QAction * res = menu.exec( e->globalPos() );
                        if ( res )
                        {
                            if ( res == actProcessLink )
                            {
                                d->document->processAction( link );
                            }
                            else if ( res == actCopyLinkLocation )
                            {
                                const Okular::BrowseAction * browseLink = static_cast< const Okular::BrowseAction * >( link );
                                QClipboard *cb = QApplication::clipboard();
                                cb->setText( browseLink->url(), QClipboard::Clipboard );
                                if ( cb->supportsSelection() )
                                    cb->setText( browseLink->url(), QClipboard::Selection );
                            }
                            else if ( res == actStopSound )
                            {
                                Okular::AudioPlayer::instance()->stopPlaybacks();
                            }
                        }
                    }
                    else
                    {
                        // a link can move us to another page or even to another document, there's no point in trying to
                        //  process the click on the image once we have processes the click on the link
                        rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
                        if ( rect )
                        {
                            // handle right click over a image
                        }
                        else
                        {
                            // right click (if not within 5 px of the press point, the mode
                            // had been already changed to 'Selection' instead of 'Normal')
                            emit rightClick( pageItem->page(), e->globalPos() );
                        }
                    }
                }
                else
                {
                    // right click (if not within 5 px of the press point, the mode
                    // had been already changed to 'Selection' instead of 'Normal')
                    emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() );
                }
            }
            }break;

        case Okular::Settings::EnumMouseMode::Zoom:
            // if a selection rect has been defined, zoom into it
            if ( leftButton && d->mouseSelecting )
            {
                QRect selRect = d->mouseSelectionRect.normalized();
                if ( selRect.width() <= 8 && selRect.height() <= 8 )
                {
                    selectionClear();
                    break;
                }

                // find out new zoom ratio and normalized view center (relative to the contentsRect)
                double zoom = qMin( (double)viewport()->width() / (double)selRect.width(), (double)viewport()->height() / (double)selRect.height() );
                double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)contentAreaWidth());
                double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)contentAreaHeight());

                const float upperZoomLimit = d->document->supportsTiles() ? 16.0 : 4.0;
                if ( d->zoomFactor <= upperZoomLimit || zoom <= 1.0 )
                {
                    d->zoomFactor *= zoom;
                    viewport()->setUpdatesEnabled( false );
                    updateZoom( ZoomRefreshCurrent );
                    viewport()->setUpdatesEnabled( true );
                }

                // recenter view and update the viewport
                center( (int)(nX * contentAreaWidth()), (int)(nY * contentAreaHeight()) );
                viewport()->update();

                // hide message box and delete overlay window
                selectionClear();
            }
            break;

        case Okular::Settings::EnumMouseMode::Magnifier:
            d->magnifierView->hide();
            break;

        case Okular::Settings::EnumMouseMode::RectSelect:
        {
            // if mouse is released and selection is null this is a rightClick
            if ( rightButton && !d->mouseSelecting )
            {
                PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
                emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() );
                break;
            }

            // if a selection is defined, display a popup
            if ( (!leftButton && !d->aPrevAction) || (!rightButton && d->aPrevAction) ||
                 !d->mouseSelecting )
                break;

            QRect selectionRect = d->mouseSelectionRect.normalized();
            if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 )
            {
                selectionClear();
                if ( d->aPrevAction )
                {
                    d->aPrevAction->trigger();
                    d->aPrevAction = 0;
                }
                break;
            }

            // if we support text generation
            QString selectedText;
            if (d->document->supportsSearching())
            {
                // grab text in selection by extracting it from all intersected pages
                const Okular::Page * okularPage=0;
                QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd();
                for ( ; iIt != iEnd; ++iIt )
                {
                    PageViewItem * item = *iIt;
                    if ( !item->isVisible() )
                        continue;

                    const QRect & itemRect = item->croppedGeometry();
                    if ( selectionRect.intersects( itemRect ) )
                    {
                        // request the textpage if there isn't one
                        okularPage= item->page();
                        kWarning() << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage();
                        if ( !okularPage->hasTextPage() )
                            d->document->requestTextPage( okularPage->number() );
                        // grab text in the rect that intersects itemRect
                        QRect relativeRect = selectionRect.intersect( itemRect );
                        relativeRect.translate( -item->uncroppedGeometry().topLeft() );
                        Okular::RegularAreaRect rects;
                        rects.append( Okular::NormalizedRect( relativeRect, item->uncroppedWidth(), item->uncroppedHeight() ) );
                        selectedText += okularPage->text( &rects );
                    }
                }
            }

            // popup that ask to copy:text and copy/save:image
            KMenu menu( this );
            QAction *textToClipboard = 0, *speakText = 0, *imageToClipboard = 0, *imageToFile = 0;
            if ( d->document->supportsSearching() && !selectedText.isEmpty() )
            {
                menu.addTitle( i18np( "Text (1 character)", "Text (%1 characters)", selectedText.length() ) );
                textToClipboard = menu.addAction( KIcon("edit-copy"), i18n( "Copy to Clipboard" ) );
                bool copyAllowed = d->document->isAllowed( Okular::AllowCopy );
                if ( !copyAllowed )
                {
                    textToClipboard->setEnabled( false );
                    textToClipboard->setText( i18n("Copy forbidden by DRM") );
                }
                if ( Okular::Settings::useKTTSD() )
                    speakText = menu.addAction( KIcon("text-speak"), i18n( "Speak Text" ) );
                if ( copyAllowed )
                {
                    addWebShortcutsMenu( &menu, selectedText );
                }
            }
            menu.addTitle( i18n( "Image (%1 by %2 pixels)", selectionRect.width(), selectionRect.height() ) );
            imageToClipboard = menu.addAction( KIcon("image-x-generic"), i18n( "Copy to Clipboard" ) );
            imageToFile = menu.addAction( KIcon("document-save"), i18n( "Save to File..." ) );
            QAction *choice = menu.exec( e->globalPos() );
            // check if the user really selected an action
            if ( choice )
            {
            // IMAGE operation chosen
            if ( choice == imageToClipboard || choice == imageToFile )
            {
                // renders page into a pixmap
                QPixmap copyPix( selectionRect.width(), selectionRect.height() );
                QPainter copyPainter( &copyPix );
                copyPainter.translate( -selectionRect.left(), -selectionRect.top() );
                drawDocumentOnPainter( selectionRect, &copyPainter );
                copyPainter.end();

                if ( choice == imageToClipboard )
                {
                    // [2] copy pixmap to clipboard
                    QClipboard *cb = QApplication::clipboard();
                    cb->setPixmap( copyPix, QClipboard::Clipboard );
                    if ( cb->supportsSelection() )
                        cb->setPixmap( copyPix, QClipboard::Selection );
                    d->messageWindow->display( i18n( "Image [%1x%2] copied to clipboard.", copyPix.width(), copyPix.height() ) );
                }
                else if ( choice == imageToFile )
                {
                    // [3] save pixmap to file
                    QString fileName = KFileDialog::getSaveFileName( KUrl(), "image/png image/jpeg", this, QString(),
                                                                     KFileDialog::ConfirmOverwrite );
                    if ( fileName.isEmpty() )
                        d->messageWindow->display( i18n( "File not saved." ), QString(), PageViewMessage::Warning );
                    else
                    {
                        KMimeType::Ptr mime = KMimeType::findByUrl( fileName );
                        QString type;
                        if ( !mime || mime == KMimeType::defaultMimeTypePtr() )
                            type = "PNG";
                        else
                            type = mime->name().section( '/', -1 ).toUpper();
                        copyPix.save( fileName, qPrintable( type ) );
                        d->messageWindow->display( i18n( "Image [%1x%2] saved to %3 file.", copyPix.width(), copyPix.height(), type ) );
                    }
                }
            }
            // TEXT operation chosen
            else
            {
                if ( choice == textToClipboard )
                {
                    // [1] copy text to clipboard
                    QClipboard *cb = QApplication::clipboard();
                    cb->setText( selectedText, QClipboard::Clipboard );
                    if ( cb->supportsSelection() )
                        cb->setText( selectedText, QClipboard::Selection );
                }
                else if ( choice == speakText )
                {
                    // [2] speech selection using KTTSD
                    d->tts()->say( selectedText );
                }
            }
            }
            // clear widget selection and invalidate rect
            selectionClear();

            // restore previous action if came from it using right button
            if ( d->aPrevAction )
            {
                d->aPrevAction->trigger();
                d->aPrevAction = 0;
            }
            }break;

        case Okular::Settings::EnumMouseMode::TableSelect:
        {
            // if mouse is released and selection is null this is a rightClick
            if ( rightButton && !d->mouseSelecting )
            {
                PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
                emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() );
                break;
            }

            QRect selectionRect = d->mouseSelectionRect.normalized();
            if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 && d->tableSelectionParts.isEmpty() )
            {
                selectionClear();
                if ( d->aPrevAction )
                {
                    d->aPrevAction->trigger();
                    d->aPrevAction = 0;
                }
                break;
            }

            if (d->mouseSelecting) {
                // break up the selection into page-relative pieces
                d->tableSelectionParts.clear();
                const Okular::Page * okularPage=0;
                QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd();
                for ( ; iIt != iEnd; ++iIt )
                {
                    PageViewItem * item = *iIt;
                    if ( !item->isVisible() )
                        continue;

                    const QRect & itemRect = item->croppedGeometry();
                    if ( selectionRect.intersects( itemRect ) )
                    {
                        // request the textpage if there isn't one
                        okularPage= item->page();
                        kWarning() << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage();
                        if ( !okularPage->hasTextPage() )
                            d->document->requestTextPage( okularPage->number() );
                        // grab text in the rect that intersects itemRect
                        QRect rectInItem = selectionRect.intersect( itemRect );
                        rectInItem.translate( -item->uncroppedGeometry().topLeft() );
                        QRect rectInSelection = selectionRect.intersect( itemRect );
                        rectInSelection.translate( -selectionRect.topLeft() );
                        d->tableSelectionParts.append(
                            TableSelectionPart(
                                item,
                                Okular::NormalizedRect( rectInItem, item->uncroppedWidth(), item->uncroppedHeight() ),
                                Okular::NormalizedRect( rectInSelection, selectionRect.width(), selectionRect.height() )
                            )
                        );
                    }
                }

                QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( 0, 0, 1, 1 );
                updatedRect.translate( -contentAreaPosition() );
                d->mouseSelecting = false;
                d->mouseSelectionRect.setCoords( 0, 0, 0, 0 );
                d->tableSelectionCols.clear();
                d->tableSelectionRows.clear();
                guessTableDividers();
                viewport()->update( updatedRect );
            }

            if ( !d->document->isAllowed( Okular::AllowCopy ) ) {
                d->messageWindow->display( i18n("Copy forbidden by DRM"), QString(), PageViewMessage::Info, -1 );
                break;
            }

            QString selText;
            QString selHtml;
            QList<double> xs = d->tableSelectionCols;
            QList<double> ys = d->tableSelectionRows;
            xs.prepend(0.0);
            xs.append(1.0);
            ys.prepend(0.0);
            ys.append(1.0);
            selHtml = "<html><head>"
                      "<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">"
                      "</head><body><table>";
            for (int r=0; r+1<ys.length(); r++) {
                selHtml += "<tr>";
                for (int c=0; c+1<xs.length(); c++) {
                    Okular::NormalizedRect cell(xs[c], ys[r], xs[c+1], ys[r+1]);
                    if (c) selText += '\t';
                    QString txt;
                    foreach (const TableSelectionPart &tsp, d->tableSelectionParts) {
                        // first, crop the cell to this part
                        if (!tsp.rectInSelection.intersects(cell))
                            continue;
                        Okular::NormalizedRect cellPart = tsp.rectInSelection & cell; // intersection

                        // second, convert it from table coordinates to part coordinates
                        cellPart.left -= tsp.rectInSelection.left;
                        cellPart.left /= (tsp.rectInSelection.right - tsp.rectInSelection.left);
                        cellPart.right -= tsp.rectInSelection.left;
                        cellPart.right /= (tsp.rectInSelection.right - tsp.rectInSelection.left);
                        cellPart.top -= tsp.rectInSelection.top;
                        cellPart.top /= (tsp.rectInSelection.bottom - tsp.rectInSelection.top);
                        cellPart.bottom -= tsp.rectInSelection.top;
                        cellPart.bottom /= (tsp.rectInSelection.bottom - tsp.rectInSelection.top);

                        // third, convert from part coordinates to item coordinates
                        cellPart.left *= (tsp.rectInItem.right - tsp.rectInItem.left);
                        cellPart.left += tsp.rectInItem.left;
                        cellPart.right *= (tsp.rectInItem.right - tsp.rectInItem.left);
                        cellPart.right += tsp.rectInItem.left;
                        cellPart.top *= (tsp.rectInItem.bottom - tsp.rectInItem.top);
                        cellPart.top += tsp.rectInItem.top;
                        cellPart.bottom *= (tsp.rectInItem.bottom - tsp.rectInItem.top);
                        cellPart.bottom += tsp.rectInItem.top;

                        // now get the text
                        Okular::RegularAreaRect rects;
                        rects.append( cellPart );
                        txt += tsp.item->page()->text( &rects, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour );
                    }
                    QString html = txt;
                    selText += txt.replace('\n', ' ');
                    html.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;");
                    // Remove newlines, do not turn them into <br>, because
                    // Excel interprets <br> within cell as new cell...
                    html.replace('\n', " ");
                    selHtml += "<td>"+html+"</td>";
                }
                selText += '\n';
                selHtml += "</tr>\n";
            }
            selHtml += "</table></body></html>\n";


            QClipboard *cb = QApplication::clipboard();
            QMimeData *md = new QMimeData();
            md->setText(selText);
            md->setHtml(selHtml);
            cb->setMimeData( md, QClipboard::Clipboard );
            if ( cb->supportsSelection() )
                cb->setMimeData( md, QClipboard::Selection );

        }break;
            case Okular::Settings::EnumMouseMode::TextSelect:
                if ( d->mouseTextSelecting )
                {
                    d->mouseTextSelecting = false;
//                    textSelectionClear();
                    if ( d->document->isAllowed( Okular::AllowCopy ) )
                    {
                        const QString text = d->selectedText();
                        if ( !text.isEmpty() )
                        {
                            QClipboard *cb = QApplication::clipboard();
                            if ( cb->supportsSelection() )
                                cb->setText( text, QClipboard::Selection );
                        }
                    }
                }
                else if ( !d->mousePressPos.isNull() && rightButton )
                {
                    PageViewItem* item = pickItemOnPoint(eventPos.x(),eventPos.y());
                    const Okular::Page *page;
                    //if there is text selected in the page
                    if (item && (page = item->page())->textSelection())
                    {
                        KMenu menu( this );
                        QAction *textToClipboard = menu.addAction( KIcon( "edit-copy" ), i18n( "Copy Text" ) );
                        QAction *speakText = 0;
                        QAction *httpLink = 0;
                        if ( Okular::Settings::useKTTSD() )
                            speakText = menu.addAction( KIcon( "text-speak" ), i18n( "Speak Text" ) );
                        if ( !d->document->isAllowed( Okular::AllowCopy ) )
                        {
                            textToClipboard->setEnabled( false );
                            textToClipboard->setText( i18n("Copy forbidden by DRM") );
                        }
                        else
                        {
                            addWebShortcutsMenu( &menu, d->selectedText() );
                        }
                        const QString url = UrlUtils::getUrl( d->selectedText() );
                        if ( !url.isEmpty() )
                        {
                            const QString squeezedText = KStringHandler::rsqueeze( url, 30 );
                            httpLink = menu.addAction( i18n( "Go to '%1'", squeezedText ) );
                        }
                        QAction *choice = menu.exec( e->globalPos() );
                        // check if the user really selected an action
                        if ( choice )
                        {
                            if ( choice == textToClipboard )
                                copyTextSelection();
                            else if ( choice == speakText )
                            {
                                const QString text = d->selectedText();
                                d->tts()->say( text );
                            }
                            else if ( choice == httpLink )
                                new KRun( KUrl( url ), this );
                        }
                    }
                }
            break;
    }

    // reset mouse press / 'drag start' position
    d->mousePressPos = QPoint();
}

void PageView::guessTableDividers()
{
    QList< QPair<double, int> > colTicks, rowTicks, colSelectionTicks, rowSelectionTicks;

    foreach ( const TableSelectionPart& tsp, d->tableSelectionParts )
    {
        // add ticks for the edges of this area...
        colSelectionTicks.append( qMakePair( tsp.rectInSelection.left,   +1 ) );
        colSelectionTicks.append( qMakePair( tsp.rectInSelection.right,  -1 ) );
        rowSelectionTicks.append( qMakePair( tsp.rectInSelection.top,    +1 ) );
        rowSelectionTicks.append( qMakePair( tsp.rectInSelection.bottom, -1 ) );

        // get the words in this part
        Okular::RegularAreaRect rects;
        rects.append( tsp.rectInItem );
        const Okular::TextEntity::List words = tsp.item->page()->words( &rects, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour );

        foreach (Okular::TextEntity *te, words)
        {
            if (te->text().isEmpty()) {
                delete te;
                continue;
            }

            Okular::NormalizedRect wordArea = *te->area();

            // convert it from item coordinates to part coordinates
            wordArea.left -= tsp.rectInItem.left;
            wordArea.left /= (tsp.rectInItem.right - tsp.rectInItem.left);
            wordArea.right -= tsp.rectInItem.left;
            wordArea.right /= (tsp.rectInItem.right - tsp.rectInItem.left);
            wordArea.top -= tsp.rectInItem.top;
            wordArea.top /= (tsp.rectInItem.bottom - tsp.rectInItem.top);
            wordArea.bottom -= tsp.rectInItem.top;
            wordArea.bottom /= (tsp.rectInItem.bottom - tsp.rectInItem.top);

            // convert from part coordinates to table coordinates
            wordArea.left *= (tsp.rectInSelection.right - tsp.rectInSelection.left);
            wordArea.left += tsp.rectInSelection.left;
            wordArea.right *= (tsp.rectInSelection.right - tsp.rectInSelection.left);
            wordArea.right += tsp.rectInSelection.left;
            wordArea.top *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top);
            wordArea.top += tsp.rectInSelection.top;
            wordArea.bottom *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top);
            wordArea.bottom += tsp.rectInSelection.top;

            // add to the ticks arrays...
            colTicks.append( qMakePair( wordArea.left,   +1) );
            colTicks.append( qMakePair( wordArea.right,  -1) );
            rowTicks.append( qMakePair( wordArea.top,    +1) );
            rowTicks.append( qMakePair( wordArea.bottom, -1) );

            delete te;
        }
    }

    int tally = 0;

    qSort( colSelectionTicks );
    qSort( rowSelectionTicks );

    for (int i = 0; i < colSelectionTicks.length(); ++i)
    {
        tally += colSelectionTicks[i].second;
        if ( tally == 0 && i + 1 < colSelectionTicks.length() && colSelectionTicks[i+1].first != colSelectionTicks[i].first)
        {
            colTicks.append( qMakePair( colSelectionTicks[i].first,   +1 ) );
            colTicks.append( qMakePair( colSelectionTicks[i+1].first, -1 ) );
        }
    }
    Q_ASSERT( tally == 0 );

    for (int i = 0; i < rowSelectionTicks.length(); ++i)
    {
        tally += rowSelectionTicks[i].second;
        if ( tally == 0 && i + 1 < rowSelectionTicks.length() && rowSelectionTicks[i+1].first != rowSelectionTicks[i].first) {
            rowTicks.append( qMakePair( rowSelectionTicks[i].first,   +1 ) );
            rowTicks.append( qMakePair( rowSelectionTicks[i+1].first, -1 ) );
        }
    }
    Q_ASSERT( tally == 0 );

    qSort( colTicks );
    qSort( rowTicks );

    for (int i = 0; i < colTicks.length(); ++i)
    {
        tally += colTicks[i].second;
        if ( tally == 0 && i + 1 < colTicks.length() && colTicks[i+1].first != colTicks[i].first)
        {
            d->tableSelectionCols.append( (colTicks[i].first+colTicks[i+1].first) / 2 );
            d->tableDividersGuessed = true;
        }
    }
    Q_ASSERT( tally == 0 );

    for (int i = 0; i < rowTicks.length(); ++i)
    {
        tally += rowTicks[i].second;
        if ( tally == 0 && i + 1 < rowTicks.length() && rowTicks[i+1].first != rowTicks[i].first)
        {
            d->tableSelectionRows.append( (rowTicks[i].first+rowTicks[i+1].first) / 2 );
            d->tableDividersGuessed = true;
        }
    }
    Q_ASSERT( tally == 0 );
}

void PageView::mouseDoubleClickEvent( QMouseEvent * e )
{
    if ( e->button() == Qt::LeftButton )
    {
        const QPoint eventPos = contentAreaPoint( e->pos() );
        PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
        if ( pageItem )
        {
            // find out normalized mouse coords inside current item
            double nX = pageItem->absToPageX(eventPos.x());
            double nY = pageItem->absToPageY(eventPos.y());

            if ( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TextSelect ) {
                textSelectionClear();

                Okular::RegularAreaRect *wordRect = pageItem->page()->wordAt( Okular::NormalizedPoint( nX, nY ) );
                if ( wordRect )
                {
                    // TODO words with hyphens across pages
                    d->document->setPageTextSelection( pageItem->pageNumber(), wordRect, palette().color( QPalette::Active, QPalette::Highlight ) );
                    d->pagesWithTextSelection << pageItem->pageNumber();
                    if ( d->document->isAllowed( Okular::AllowCopy ) )
                    {
                        const QString text = d->selectedText();
                        if ( !text.isEmpty() )
                        {
                            QClipboard *cb = QApplication::clipboard();
                            if ( cb->supportsSelection() )
                                cb->setText( text, QClipboard::Selection );
                        }
                    }
                    return;
                }
            }

            const QRect & itemRect = pageItem->uncroppedGeometry();
            Okular::Annotation * ann = 0;
            const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() );
            if ( orect )
                ann = ( (Okular::AnnotationObjectRect *)orect )->annotation();
            if ( ann && ann->subType() != Okular::Annotation::AWidget )
            {
                openAnnotationWindow( ann, pageItem->pageNumber() );
            }
        }
    }
}

void PageView::wheelEvent( QWheelEvent *e )
{
    // don't perform any mouse action when viewport is autoscrolling
    if ( d->viewportMoveActive )
        return;

    if ( !d->document->isOpened() )
    {
        QAbstractScrollArea::wheelEvent( e );
        return;
    }

    int delta = e->delta(),
        vScroll = verticalScrollBar()->value();
    e->accept();
    if ( (e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) {
        if ( e->delta() < 0 )
            slotZoomOut();
        else
            slotZoomIn();
    }
    else if ( delta <= -120 && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->maximum() )
    {
        // go to next page
        if ( (int)d->document->currentPage() < d->items.count() - 1 )
        {
            // more optimized than document->setNextPage and then move view to top
            Okular::DocumentViewport newViewport = d->document->viewport();
            newViewport.pageNumber += viewColumns();
            if ( newViewport.pageNumber >= (int)d->items.count() )
                newViewport.pageNumber = d->items.count() - 1;
            newViewport.rePos.enabled = true;
            newViewport.rePos.normalizedY = 0.0;
            d->document->setViewport( newViewport );
        }
    }
    else if ( delta >= 120 && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->minimum() )
    {
        // go to prev page
        if ( d->document->currentPage() > 0 )
        {
            // more optimized than document->setPrevPage and then move view to bottom
            Okular::DocumentViewport newViewport = d->document->viewport();
            newViewport.pageNumber -= viewColumns();
            if ( newViewport.pageNumber < 0 )
                newViewport.pageNumber = 0;
            newViewport.rePos.enabled = true;
            newViewport.rePos.normalizedY = 1.0;
            d->document->setViewport( newViewport );
        }
    }
    else
        QAbstractScrollArea::wheelEvent( e );

    updateCursor();
}

bool PageView::viewportEvent( QEvent * e )
{
    if ( e->type() == QEvent::ToolTip && Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Browse )
    {
        QHelpEvent * he = static_cast< QHelpEvent* >( e );
        const QPoint eventPos = contentAreaPoint( he->pos() );
        PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() );
        const Okular::ObjectRect * rect = 0;
        const Okular::Action * link = 0;
        const Okular::Annotation * ann = 0;
        if ( pageItem )
        {
            double nX = pageItem->absToPageX( eventPos.x() );
            double nY = pageItem->absToPageY( eventPos.y() );
            rect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
            if ( rect )
                ann = static_cast< const Okular::AnnotationObjectRect * >( rect )->annotation();
            else
            {
                rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
                if ( rect )
                    link = static_cast< const Okular::Action * >( rect->object() );
            }
        }

        if ( ann && ann->subType() != Okular::Annotation::AWidget )
        {
            QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
            r.translate( pageItem->uncroppedGeometry().topLeft() );
            r.translate( -contentAreaPosition() );
            QString tip = GuiUtils::prettyToolTip( ann );
            QToolTip::showText( he->globalPos(), tip, viewport(), r );
        }
        else if ( link )
        {
            QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
            r.translate( pageItem->uncroppedGeometry().topLeft() );
            r.translate( -contentAreaPosition() );
            QString tip = link->actionTip();
            if ( !tip.isEmpty() )
                QToolTip::showText( he->globalPos(), tip, viewport(), r );
        }
        e->accept();
        return true;
    }
    else
        // do not stop the event
        return QAbstractScrollArea::viewportEvent( e );
}

void PageView::scrollContentsBy( int dx, int dy )
{
    const QRect r = viewport()->rect();
    viewport()->scroll( dx, dy, r );
    // HACK manually repaint the damaged regions, as it seems some updates are missed
    // thus leaving artifacts around
    QRegion rgn( r );
    rgn -= rgn & r.translated( dx, dy );
    foreach ( const QRect &rect, rgn.rects() )
        viewport()->repaint( rect );
}
//END widget events

QList< Okular::RegularAreaRect * > PageView::textSelections( const QPoint& start, const QPoint& end, int& firstpage )
{
    firstpage = -1;
    QList< Okular::RegularAreaRect * > ret;
    QSet< int > affectedItemsSet;
    QRect selectionRect = QRect( start, end ).normalized();
    foreach( PageViewItem * item, d->items )
    {
        if ( item->isVisible() && selectionRect.intersects( item->croppedGeometry() ) )
            affectedItemsSet.insert( item->pageNumber() );
    }
#ifdef PAGEVIEW_DEBUG
    kDebug() << ">>>> item selected by mouse:" << affectedItemsSet.count();
#endif

    if ( !affectedItemsSet.isEmpty() )
    {
        // is the mouse drag line the ne-sw diagonal of the selection rect?
        bool direction_ne_sw = start == selectionRect.topRight() || start == selectionRect.bottomLeft();

        int tmpmin = d->document->pages();
        int tmpmax = 0;
        foreach( int p, affectedItemsSet )
        {
            if ( p < tmpmin ) tmpmin = p;
            if ( p > tmpmax ) tmpmax = p;
        }

        PageViewItem * a = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.right() : selectionRect.left() ), (int)selectionRect.top() );
        int min = a && ( a->pageNumber() != tmpmax ) ? a->pageNumber() : tmpmin;
        PageViewItem * b = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.left() : selectionRect.right() ), (int)selectionRect.bottom() );
        int max = b && ( b->pageNumber() != tmpmin ) ? b->pageNumber() : tmpmax;

        QList< int > affectedItemsIds;
        for ( int i = min; i <= max; ++i )
            affectedItemsIds.append( i );
#ifdef PAGEVIEW_DEBUG
        kDebug() << ">>>> pages:" << affectedItemsIds;
#endif
        firstpage = affectedItemsIds.first();

        if ( affectedItemsIds.count() == 1 )
        {
            PageViewItem * item = d->items[ affectedItemsIds.first() ];
            selectionRect.translate( -item->uncroppedGeometry().topLeft() );
            ret.append( textSelectionForItem( item,
                direction_ne_sw ? selectionRect.topRight() : selectionRect.topLeft(),
                direction_ne_sw ? selectionRect.bottomLeft() : selectionRect.bottomRight() ) );
        }
        else if ( affectedItemsIds.count() > 1 )
        {
            // first item
            PageViewItem * first = d->items[ affectedItemsIds.first() ];
            QRect geom = first->croppedGeometry().intersect( selectionRect ).translated( -first->uncroppedGeometry().topLeft() );
            ret.append( textSelectionForItem( first,
                selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.topRight() : geom.topLeft() ) : ( direction_ne_sw ? geom.bottomRight() : geom.bottomLeft() ),
                QPoint() ) );
            // last item
            PageViewItem * last = d->items[ affectedItemsIds.last() ];
            geom = last->croppedGeometry().intersect( selectionRect ).translated( -last->uncroppedGeometry().topLeft() );
            // the last item needs to appended at last...
            Okular::RegularAreaRect * lastArea = textSelectionForItem( last,
                QPoint(),
                selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.bottomLeft() : geom.bottomRight() ) : ( direction_ne_sw ? geom.topLeft() : geom.topRight() ) );
            affectedItemsIds.removeFirst();
            affectedItemsIds.removeLast();
            // item between the two above
            foreach( int page, affectedItemsIds )
            {
                ret.append( textSelectionForItem( d->items[ page ] ) );
            }
            ret.append( lastArea );
        }
    }
    return ret;
}


void PageView::drawDocumentOnPainter( const QRect & contentsRect, QPainter * p )
{
    QColor backColor = viewport()->palette().color( QPalette::Dark );

    // when checking if an Item is contained in contentsRect, instead of
    // growing PageViewItems rects (for keeping outline into account), we
    // grow the contentsRect
    QRect checkRect = contentsRect;
    checkRect.adjust( -3, -3, 1, 1 );

    // create a region from which we'll subtract painted rects
    QRegion remainingArea( contentsRect );

    // iterate over all items painting the ones intersecting contentsRect
    QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd();
    for ( ; iIt != iEnd; ++iIt )
    {
        // check if a piece of the page intersects the contents rect
        if ( !(*iIt)->isVisible() || !(*iIt)->croppedGeometry().intersects( checkRect ) )
            continue;

        // get item and item's outline geometries
        PageViewItem * item = *iIt;
        QRect itemGeometry = item->croppedGeometry(),
              outlineGeometry = itemGeometry;
        outlineGeometry.adjust( -1, -1, 3, 3 );

        // move the painter to the top-left corner of the real page
        p->save();
        p->translate( itemGeometry.left(), itemGeometry.top() );

        // draw the page outline (black border and 2px bottom-right shadow)
        if ( !itemGeometry.contains( contentsRect ) )
        {
            int itemWidth = itemGeometry.width(),
                itemHeight = itemGeometry.height();
            // draw simple outline
            p->setPen( Qt::black );
            p->drawRect( -1, -1, itemWidth + 1, itemHeight + 1 );
            // draw bottom/right gradient
            static int levels = 2;
            int r = backColor.red() / (levels + 2) + 6,
                g = backColor.green() / (levels + 2) + 6,
                b = backColor.blue() / (levels + 2) + 6;
            for ( int i = 0; i < levels; i++ )
            {
                p->setPen( QColor( r * (i+2), g * (i+2), b * (i+2) ) );
                p->drawLine( i, i + itemHeight + 1, i + itemWidth + 1, i + itemHeight + 1 );
                p->drawLine( i + itemWidth + 1, i, i + itemWidth + 1, i + itemHeight );
                p->setPen( backColor );
                p->drawLine( -1, i + itemHeight + 1, i - 1, i + itemHeight + 1 );
                p->drawLine( i + itemWidth + 1, -1, i + itemWidth + 1, i - 1 );
            }
        }

        // draw the page using the PagePainter with all flags active
        if ( contentsRect.intersects( itemGeometry ) )
        {
            Okular::NormalizedPoint *viewPortPoint = 0;
            Okular::NormalizedPoint point( d->lastSourceLocationViewportNormalizedX, d->lastSourceLocationViewportNormalizedY );
            if( Okular::Settings::showSourceLocationsGraphically()
                && item->pageNumber() ==  d->lastSourceLocationViewportPageNumber )
            {
                viewPortPoint = &point;
            }
            QRect pixmapRect = contentsRect.intersect( itemGeometry );
            pixmapRect.translate( -item->croppedGeometry().topLeft() );
            PagePainter::paintCroppedPageOnPainter( p, item->page(), this, pageflags,
                item->uncroppedWidth(), item->uncroppedHeight(), pixmapRect,
                item->crop(), viewPortPoint );
        }

        // remove painted area from 'remainingArea' and restore painter
        remainingArea -= outlineGeometry.intersect( contentsRect );
        p->restore();
    }

    // fill with background color the unpainted area
    const QVector<QRect> &backRects = remainingArea.rects();
    int backRectsNumber = backRects.count();
    for ( int jr = 0; jr < backRectsNumber; jr++ )
        p->fillRect( backRects[ jr ], backColor );
}

void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight )
{
    const Okular::Page * okularPage = item->page();
    double width = okularPage->width(),
           height = okularPage->height(),
           zoom = d->zoomFactor;
    Okular::NormalizedRect crop( 0., 0., 1., 1. );

    // Handle cropping
    if ( Okular::Settings::trimMargins() && okularPage->isBoundingBoxKnown()
         && !okularPage->boundingBox().isNull() )
    {
        crop = okularPage->boundingBox();

        // Rotate the bounding box
        for ( int i = okularPage->rotation(); i > 0; --i )
        {
            Okular::NormalizedRect rot = crop;
            crop.left   = 1 - rot.bottom;
            crop.top    = rot.left;
            crop.right  = 1 - rot.top;
            crop.bottom = rot.right;
        }

        // Expand the crop slightly beyond the bounding box
        static const double cropExpandRatio = 0.04;
        const double cropExpand = cropExpandRatio * ( (crop.right-crop.left) + (crop.bottom-crop.top) ) / 2;
        crop = Okular::NormalizedRect(
            crop.left - cropExpand,
            crop.top - cropExpand,
            crop.right + cropExpand,
            crop.bottom + cropExpand ) & Okular::NormalizedRect( 0, 0, 1, 1 );

        // We currently generate a larger image and then crop it, so if the
        // crop rect is very small the generated image is huge. Hence, we shouldn't
        // let the crop rect become too small.
        // Make sure we crop by at most 50% in either dimension:
        static const double minCropRatio = 0.5;
        if ( ( crop.right - crop.left ) < minCropRatio )
        {
            const double newLeft = ( crop.left + crop.right ) / 2 - minCropRatio/2;
            crop.left = qMax( 0.0, qMin( 1.0 - minCropRatio, newLeft ) );
            crop.right = crop.left + minCropRatio;
        }
        if ( ( crop.bottom - crop.top ) < minCropRatio )
        {
            const double newTop = ( crop.top + crop.bottom ) / 2 - minCropRatio/2;
            crop.top = qMax( 0.0, qMin( 1.0 - minCropRatio, newTop ) );
            crop.bottom = crop.top + minCropRatio;
        }

        width *= ( crop.right - crop.left );
        height *= ( crop.bottom - crop.top );
#ifdef PAGEVIEW_DEBUG
        kDebug() << "Cropped page" << okularPage->number() << "to" << crop
                 << "width" << width << "height" << height << "by bbox" << okularPage->boundingBox();
#endif
    }

    if ( d->zoomMode == ZoomFixed )
    {
        width *= zoom;
        height *= zoom;
        item->setWHZC( (int)width, (int)height, d->zoomFactor, crop );
    }
    else if ( d->zoomMode == ZoomFitWidth )
    {
        height = ( height / width ) * colWidth;
        zoom = (double)colWidth / width;
        item->setWHZC( colWidth, (int)height, zoom, crop );
        if ((uint)item->pageNumber() == d->document->currentPage())
            d->zoomFactor = zoom;
    }
    else if ( d->zoomMode == ZoomFitPage )
    {
        const double scaleW = (double)colWidth / (double)width;
        const double scaleH = (double)rowHeight / (double)height;
        zoom = qMin( scaleW, scaleH );
        item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop );
        if ((uint)item->pageNumber() == d->document->currentPage())
            d->zoomFactor = zoom;
    }
    else if ( d->zoomMode == ZoomFitAuto )
    {
        const double aspectRatioRelation = 1.25; // relation between aspect ratios for "auto fit"
        const double uiAspect = (double)rowHeight / (double)colWidth;
        const double pageAspect = (double)height / (double)width;
        const double rel = uiAspect / pageAspect;

        const bool isContinuous = Okular::Settings::viewContinuous();
        if ( !isContinuous && rel > aspectRatioRelation )
        {
            // UI space is relatively much higher than the page
            zoom = (double)rowHeight / (double)height;
        }
        else if ( rel < 1.0 / aspectRatioRelation )
        {
            // UI space is relatively much wider than the page in relation
            zoom = (double)colWidth / (double)width;
        }
        else
        {
            // aspect ratios of page and UI space are very similar
            const double scaleW = (double)colWidth / (double)width;
            const double scaleH = (double)rowHeight / (double)height;
            zoom = qMin( scaleW, scaleH );
        }
        item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop );
        if ((uint)item->pageNumber() == d->document->currentPage())
            d->zoomFactor = zoom;
    }
#ifndef NDEBUG
    else
        kDebug() << "calling updateItemSize with unrecognized d->zoomMode!";
#endif
}

PageViewItem * PageView::pickItemOnPoint( int x, int y )
{
    PageViewItem * item = 0;
    QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd();
    for ( ; iIt != iEnd; ++iIt )
    {
        PageViewItem * i = *iIt;
        const QRect & r = i->croppedGeometry();
        if ( x < r.right() && x > r.left() && y < r.bottom() )
        {
            if ( y > r.top() )
                item = i;
            break;
        }
    }
    return item;
}

void PageView::textSelectionClear()
{
   // something to clear
    if ( !d->pagesWithTextSelection.isEmpty() )
    {
        QSet< int >::ConstIterator it = d->pagesWithTextSelection.constBegin(), itEnd = d->pagesWithTextSelection.constEnd();
        for ( ; it != itEnd; ++it )
            d->document->setPageTextSelection( *it, 0, QColor() );
        d->pagesWithTextSelection.clear();
    }
}

void PageView::selectionStart( const QPoint & pos, const QColor & color, bool /*aboveAll*/ )
{
    selectionClear();
    d->mouseSelecting = true;
    d->mouseSelectionRect.setRect( pos.x(), pos.y(), 1, 1 );
    d->mouseSelectionColor = color;
    // ensures page doesn't scroll
    if ( d->autoScrollTimer )
    {
        d->scrollIncrement = 0;
        d->autoScrollTimer->stop();
    }
}

void PageView::scrollPosIntoView( const QPoint & pos )
{
    if (pos.x() < horizontalScrollBar()->value()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value());
    else if (horizontalScrollBar()->value() + viewport()->width() < pos.x()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value() - viewport()->width());
    else d->dragScrollVector.setX(0);

    if (pos.y() < verticalScrollBar()->value()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value());
    else if (verticalScrollBar()->value() + viewport()->height() < pos.y()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value() - viewport()->height());
    else d->dragScrollVector.setY(0);

    if (d->dragScrollVector != QPoint(0, 0))
    {
        if (!d->dragScrollTimer.isActive()) d->dragScrollTimer.start(100);
    }
    else d->dragScrollTimer.stop();
}

void PageView::updateSelection( const QPoint & pos )
{
    if ( d->mouseSelecting )
    {
        scrollPosIntoView( pos );
        // update the selection rect
        QRect updateRect = d->mouseSelectionRect;
        d->mouseSelectionRect.setBottomLeft( pos );
        updateRect |= d->mouseSelectionRect;
        updateRect.translate( -contentAreaPosition() );
        viewport()->update( updateRect.adjusted( -1, -1, 1, 1 ) );
    }
    else if ( d->mouseTextSelecting)
    {
        scrollPosIntoView( pos );
        int first = -1;
        const QList< Okular::RegularAreaRect * > selections = textSelections( pos, d->mouseSelectPos, first );
        QSet< int > pagesWithSelectionSet;
        for ( int i = 0; i < selections.count(); ++i )
            pagesWithSelectionSet.insert( i + first );

        const QSet< int > noMoreSelectedPages = d->pagesWithTextSelection - pagesWithSelectionSet;
        // clear the selection from pages not selected anymore
        foreach( int p, noMoreSelectedPages )
        {
            d->document->setPageTextSelection( p, 0, QColor() );
        }
        // set the new selection for the selected pages
        foreach( int p, pagesWithSelectionSet )
        {
            d->document->setPageTextSelection( p, selections[ p - first ], palette().color( QPalette::Active, QPalette::Highlight ) );
        }
        d->pagesWithTextSelection = pagesWithSelectionSet;
    }
}

static Okular::NormalizedPoint rotateInNormRect( const QPoint &rotated, const QRect &rect, Okular::Rotation rotation )
{
    Okular::NormalizedPoint ret;

    switch ( rotation )
    {
        case Okular::Rotation0:
            ret = Okular::NormalizedPoint( rotated.x(), rotated.y(), rect.width(), rect.height() );
            break;
        case Okular::Rotation90:
            ret = Okular::NormalizedPoint( rotated.y(), rect.width() - rotated.x(), rect.height(), rect.width() );
            break;
        case Okular::Rotation180:
            ret = Okular::NormalizedPoint( rect.width() - rotated.x(), rect.height() - rotated.y(), rect.width(), rect.height() );
            break;
        case Okular::Rotation270:
            ret = Okular::NormalizedPoint( rect.height() - rotated.y(), rotated.x(), rect.height(), rect.width() );
            break;
    }

    return ret;
}

Okular::RegularAreaRect * PageView::textSelectionForItem( PageViewItem * item, const QPoint & startPoint, const QPoint & endPoint )
{
    const QRect & geometry = item->uncroppedGeometry();
    Okular::NormalizedPoint startCursor( 0.0, 0.0 );
    if ( !startPoint.isNull() )
    {
        startCursor = rotateInNormRect( startPoint, geometry, item->page()->rotation() );
    }
    Okular::NormalizedPoint endCursor( 1.0, 1.0 );
    if ( !endPoint.isNull() )
    {
        endCursor = rotateInNormRect( endPoint, geometry, item->page()->rotation() );
    }
    Okular::TextSelection mouseTextSelectionInfo( startCursor, endCursor );

    const Okular::Page * okularPage = item->page();

    if ( !okularPage->hasTextPage() )
        d->document->requestTextPage( okularPage->number() );

    Okular::RegularAreaRect * selectionArea = okularPage->textArea( &mouseTextSelectionInfo );
#ifdef PAGEVIEW_DEBUG
    kDebug().nospace() << "text areas (" << okularPage->number() << "): " << ( selectionArea ? QString::number( selectionArea->count() ) : "(none)" );
#endif
    return selectionArea;
}

void PageView::selectionClear(const ClearMode mode)
{
    QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( 0, 0, 1, 1 );
    d->mouseSelecting = false;
    d->mouseSelectionRect.setCoords( 0, 0, 0, 0 );
    d->tableSelectionCols.clear();
    d->tableSelectionRows.clear();
    d->tableDividersGuessed = false;
    foreach (const TableSelectionPart &tsp, d->tableSelectionParts) {
        QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight());
        selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () );
        // should check whether this is on-screen here?
        updatedRect = updatedRect.united(selectionPartRect);
    }
    if ( mode != ClearOnlyDividers ) {
        d->tableSelectionParts.clear();
    }
    d->tableSelectionParts.clear();
    updatedRect.translate( -contentAreaPosition() );
    viewport()->update( updatedRect );
}

// const to be used for both zoomFactorFitMode function and slotRelayoutPages.
static const int kcolWidthMargin = 6;
static const int krowHeightMargin = 12;

double PageView::zoomFactorFitMode( ZoomMode mode )
{
    const int pageCount = d->items.count();
    if ( pageCount == 0 )
        return 0;
    const bool facingCentered = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered || (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount == 1);
    const bool overrideCentering = facingCentered && pageCount < 3;
    const int nCols = overrideCentering ? 1 : viewColumns();
    const double colWidth = viewport()->width() / nCols - kcolWidthMargin;
    const double rowHeight = viewport()->height() - krowHeightMargin;
    const PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage()) ];
    // prevent segmentation fault when openning a new document;
    if ( !currentItem )
        return 0;
    const Okular::Page * okularPage = currentItem->page();
    const double width = okularPage->width(), height = okularPage->height();
    if ( mode == ZoomFitWidth )
        return (double) colWidth / width;
    if ( mode == ZoomFitPage )
    {
        const double scaleW = (double) colWidth / (double)width;
        const double scaleH = (double) rowHeight / (double)height;
        return qMin(scaleW, scaleH);
    }
    return 0;
}

void PageView::updateZoom( ZoomMode newZoomMode )
{
    if ( newZoomMode == ZoomFixed )
    {
        if ( d->aZoom->currentItem() == 0 )
            newZoomMode = ZoomFitWidth;
        else if ( d->aZoom->currentItem() == 1 )
            newZoomMode = ZoomFitPage;
        else if ( d->aZoom->currentItem() == 2 )
            newZoomMode = ZoomFitAuto;
    }

    float newFactor = d->zoomFactor;
    QAction * checkedZoomAction = 0;
    switch ( newZoomMode )
    {
        case ZoomFixed:{ //ZoomFixed case
            QString z = d->aZoom->currentText();
            // kdelibs4 sometimes adds accelerators to actions' text directly :(
            z.remove ('&');
            z.remove ('%');
            newFactor = KGlobal::locale()->readNumber( z ) / 100.0;
            }break;
        case ZoomIn:
        case ZoomOut:{
            const float zoomFactorFitWidth = zoomFactorFitMode(ZoomFitWidth);
            const float zoomFactorFitPage = zoomFactorFitMode(ZoomFitPage);
            QVector<float> zoomValue(15);
            qCopy(kZoomValues, kZoomValues + 13, zoomValue.begin());
            zoomValue[13] = zoomFactorFitWidth;
            zoomValue[14] = zoomFactorFitPage;
            qSort(zoomValue.begin(), zoomValue.end());
            QVector<float>::iterator i;
            if ( newZoomMode == ZoomOut )
            {
                if (newFactor <= zoomValue.first())
                    return;
                i = qLowerBound(zoomValue.begin(), zoomValue.end(), newFactor) - 1;
            }
            else
            {
                if (newFactor >= zoomValue.last())
                    return;
                i = qUpperBound(zoomValue.begin(), zoomValue.end(), newFactor);
            }
            const float tmpFactor = *i;
            if ( tmpFactor == zoomFactorFitWidth )
            {
                newZoomMode = ZoomFitWidth;
                checkedZoomAction = d->aZoomFitWidth;
            }
            else if ( tmpFactor == zoomFactorFitPage )
            {
                newZoomMode = ZoomFitPage;
                checkedZoomAction = d->aZoomFitPage;
            }
            else
            {
                newFactor = tmpFactor;
                newZoomMode = ZoomFixed;
            }
            }
            break;
        case ZoomFitWidth:
            checkedZoomAction = d->aZoomFitWidth;
            break;
        case ZoomFitPage:
            checkedZoomAction = d->aZoomFitPage;
            break;
        case ZoomFitAuto:
            checkedZoomAction = d->aZoomAutoFit;
            break;
        case ZoomRefreshCurrent:
            newZoomMode = ZoomFixed;
            d->zoomFactor = -1;
            break;
    }
    const float upperZoomLimit = d->document->supportsTiles() ? 16.0 : 4.0;
    if ( newFactor > upperZoomLimit )
        newFactor = upperZoomLimit;
    if ( newFactor < 0.1 )
        newFactor = 0.1;

    if ( newZoomMode != d->zoomMode || (newZoomMode == ZoomFixed && newFactor != d->zoomFactor ) )
    {
        // rebuild layout and update the whole viewport
        d->zoomMode = newZoomMode;
        d->zoomFactor = newFactor;
        // be sure to block updates to document's viewport
        bool prevState = d->blockViewport;
        d->blockViewport = true;
        slotRelayoutPages();
        d->blockViewport = prevState;
        // request pixmaps
        slotRequestVisiblePixmaps();
        // update zoom text
        updateZoomText();
        // update actions checked state
        if ( d->aZoomFitWidth )
        {
            d->aZoomFitWidth->setChecked( checkedZoomAction == d->aZoomFitWidth );
            d->aZoomFitPage->setChecked( checkedZoomAction == d->aZoomFitPage );
            d->aZoomAutoFit->setChecked( checkedZoomAction == d->aZoomAutoFit );
        }
    }
    else if ( newZoomMode == ZoomFixed && newFactor == d->zoomFactor )
        updateZoomText();

    d->aZoomIn->setEnabled( d->zoomFactor < upperZoomLimit-0.001 );
    d->aZoomOut->setEnabled( d->zoomFactor > 0.101 );
}

void PageView::updateZoomText()
{
    // use current page zoom as zoomFactor if in ZoomFit/* mode
    if ( d->zoomMode != ZoomFixed && d->items.count() > 0 )
        d->zoomFactor = d->items[ qMax( 0, (int)d->document->currentPage() ) ]->zoomFactor();
    float newFactor = d->zoomFactor;
    d->aZoom->removeAllActions();

    // add items that describe fit actions
    QStringList translated;
    translated << i18n("Fit Width") << i18n("Fit Page") << i18n("Auto Fit");

    // add percent items
    const QString single_oh( "0" );
    int idx = 0, selIdx = 3;
    bool inserted = false; //use: "d->zoomMode != ZoomFixed" to hide Fit/* zoom ratio
    int zoomValueCount = 11;
    if ( d->document->supportsTiles() )
        zoomValueCount = 13;
    while ( idx < zoomValueCount || !inserted )
    {
        float value = idx < zoomValueCount ? kZoomValues[ idx ] : newFactor;
        if ( !inserted && newFactor < (value - 0.0001) )
            value = newFactor;
        else
            idx ++;
        if ( value > (newFactor - 0.0001) && value < (newFactor + 0.0001) )
            inserted = true;
        if ( !inserted )
            selIdx++;
        // we do not need to display 2-digit precision
        QString localValue( KGlobal::locale()->formatNumber( value * 100.0, 1 ) );
        localValue.remove( KGlobal::locale()->decimalSymbol() + single_oh );
        // remove a trailing zero in numbers like 66.70
        if ( localValue.right( 1 ) == QLatin1String( "0" ) && localValue.indexOf( KGlobal::locale()->decimalSymbol() ) > -1 )
            localValue.chop( 1 );
        translated << QString( "%1%" ).arg( localValue );
    }
    d->aZoom->setItems( translated );

    // select current item in list
    if ( d->zoomMode == ZoomFitWidth )
        selIdx = 0;
    else if ( d->zoomMode == ZoomFitPage )
        selIdx = 1;
    else if ( d->zoomMode == ZoomFitAuto )
        selIdx = 2;
    // we have to temporarily enable the actions as otherwise we can't set a new current item
    d->aZoom->setEnabled( true );
    d->aZoom->selectableActionGroup()->setEnabled( true );
    d->aZoom->setCurrentItem( selIdx );
    d->aZoom->setEnabled( d->items.size() > 0 );
    d->aZoom->selectableActionGroup()->setEnabled( d->items.size() > 0 );
}

void PageView::updateCursor()
{
    const QPoint p = contentAreaPosition() + viewport()->mapFromGlobal( QCursor::pos() );
    updateCursor( p );
}

void PageView::updateCursor( const QPoint &p )
{
    // detect the underlaying page (if present)
    PageViewItem * pageItem = pickItemOnPoint( p.x(), p.y() );

    if ( d->annotator && d->annotator->active() )
    {
        if ( pageItem || d->annotator->annotating() )
            setCursor( d->annotator->cursor() );
        else
            setCursor( Qt::ForbiddenCursor );
    }
    else if ( pageItem )
    {
        double nX = pageItem->absToPageX(p.x());
        double nY = pageItem->absToPageY(p.y());

        // if over a ObjectRect (of type Link) change cursor to hand
        if ( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TextSelect )
            setCursor( Qt::IBeamCursor );
        else if ( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Magnifier )
            setCursor( Qt::CrossCursor );
        else if ( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::RectSelect )
            setCursor( Qt::CrossCursor );
        else if ( d->mouseAnn )
            setCursor( Qt::ClosedHandCursor );
        else if ( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Browse )
        {
            const Okular::ObjectRect * linkobj = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
            const Okular::ObjectRect * annotobj = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
            if ( linkobj && !annotobj )
            {
                d->mouseOnRect = true;
                setCursor( Qt::PointingHandCursor );
            }
            else
            {
                d->mouseOnRect = false;
                if ( annotobj )
                {
                    const Okular::Annotation *annotation = static_cast< const Okular::AnnotationObjectRect * >( annotobj )->annotation();
                    if ( ( QApplication::keyboardModifiers() & Qt::ControlModifier )
                         && annotation->canBeMoved() )
                    {
                        setCursor( Qt::OpenHandCursor );
                    }
                    else if ( annotation->subType() == Okular::Annotation::AMovie )
                    {
                        d->mouseOnRect = true;
                        setCursor( Qt::PointingHandCursor );
                    }
                    else if ( annotation->subType() == Okular::Annotation::AScreen )
                    {
                        if ( GuiUtils::renditionMovieFromScreenAnnotation( static_cast< const Okular::ScreenAnnotation * >( annotation ) ) != 0 )
                        {
                            d->mouseOnRect = true;
                            setCursor( Qt::PointingHandCursor );
                        }
                    }
                    else
                    {
                        setCursor( Qt::OpenHandCursor );
                    }
                }
                else
                {
                    setCursor( Qt::OpenHandCursor );
                }
            }
        }
        else
        {
            setCursor( Qt::ArrowCursor );
        }
    }
    else
    {
        // if there's no page over the cursor and we were showing the pointingHandCursor
        // go back to the normal one
        d->mouseOnRect = false;
        setCursor( Qt::ArrowCursor );
    }
}

void PageView::moveMagnifier( const QPoint& p ) // non scaled point
{
    const int w = d->magnifierView->width() * 0.5;
    const int h = d->magnifierView->height() * 0.5;

    int x = p.x() - w;
    int y = p.y() - h;

    const int max_x = viewport()->width();
    const int max_y = viewport()->height();

    QPoint scroll(0,0);

    if (x < 0)
    {
        if (horizontalScrollBar()->value() > 0) scroll.setX(x - w);
        x = 0;
    }

    if (y < 0)
    {
        if (verticalScrollBar()->value() > 0) scroll.setY(y - h);
        y = 0;
    }

    if (p.x() + w > max_x)
    {
        if (horizontalScrollBar()->value() < horizontalScrollBar()->maximum()) scroll.setX(p.x() + 2 * w - max_x);
        x = max_x - d->magnifierView->width() - 1;
    }

    if (p.y() + h > max_y)
    {
        if (verticalScrollBar()->value() < verticalScrollBar()->maximum()) scroll.setY(p.y() + 2 * h - max_y);
        y = max_y - d->magnifierView->height() - 1;
    }

    if (!scroll.isNull())
        scrollPosIntoView(contentAreaPoint(p + scroll));

    d->magnifierView->move(x, y);
}

void PageView::updateMagnifier( const QPoint& p ) // scaled point
{
    /* translate mouse coordinates to page coordinates and inform the magnifier of the situation */
    PageViewItem *item = pickItemOnPoint(p.x(), p.y());
    if (item)
    {
        Okular::NormalizedPoint np(item->absToPageX(p.x()), item->absToPageY(p.y()));
        d->magnifierView->updateView( np, item->page() );
    }
}

int PageView::viewColumns() const
{
    int vm = Okular::Settings::viewMode();
    if (vm == Okular::Settings::EnumViewMode::Single) return 1;
    else if (vm == Okular::Settings::EnumViewMode::Facing ||
             vm == Okular::Settings::EnumViewMode::FacingFirstCentered) return 2;
    else return Okular::Settings::viewColumns();
}

void PageView::center(int cx, int cy)
{
    scrollTo( cx - viewport()->width() / 2, cy - viewport()->height() / 2 );
}

void PageView::scrollTo( int x, int y )
{
    bool prevState = d->blockPixmapsRequest;

    int newValue = -1;
    if ( x != horizontalScrollBar()->value() || y != verticalScrollBar()->value() )
        newValue = 1; // Pretend this call is the result of a scrollbar event

    d->blockPixmapsRequest = true;
    horizontalScrollBar()->setValue( x );
    verticalScrollBar()->setValue( y );

    d->blockPixmapsRequest = prevState;

    slotRequestVisiblePixmaps( newValue );
}

void PageView::toggleFormWidgets( bool on )
{
    bool somehadfocus = false;
    QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd();
    for ( ; dIt != dEnd; ++dIt )
    {
        bool hadfocus = (*dIt)->setFormWidgetsVisible( on );
        somehadfocus = somehadfocus || hadfocus;
    }
    if ( somehadfocus )
        setFocus();
    d->m_formsVisible = on;
    if ( d->aToggleForms ) // it may not exist if we are on dummy mode
    {
        if ( d->m_formsVisible )
        {
            d->aToggleForms->setText( i18n( "Hide Forms" ) );
        }
        else
        {
            d->aToggleForms->setText( i18n( "Show Forms" ) );
        }
    }
}

void PageView::resizeContentArea( const QSize & newSize )
{
    const QSize vs = viewport()->size();
    horizontalScrollBar()->setRange( 0, newSize.width() - vs.width() );
    verticalScrollBar()->setRange( 0, newSize.height() - vs.height() );
    updatePageStep();
}

void PageView::updatePageStep() {
    const QSize vs = viewport()->size();
    horizontalScrollBar()->setPageStep( vs.width() );
    verticalScrollBar()->setPageStep( vs.height() * (100 - Okular::Settings::scrollOverlap()) / 100 );
}

void PageView::addWebShortcutsMenu( KMenu * menu, const QString & text )
{
#if KDE_IS_VERSION(4,5,70)
    if ( text.isEmpty() )
    {
        return;
    }

    QString searchText = text;
    searchText = searchText.replace( '\n', ' ' ).replace( '\r', ' ' ).simplified();

    if ( searchText.isEmpty() )
    {
        return;
    }

    KUriFilterData filterData( searchText );

    filterData.setSearchFilteringOptions( KUriFilterData::RetrievePreferredSearchProvidersOnly );

    if ( KUriFilter::self()->filterSearchUri( filterData, KUriFilter::NormalTextFilter ) )
    {
        const QStringList searchProviders = filterData.preferredSearchProviders();

        if ( !searchProviders.isEmpty() )
        {
            KMenu *webShortcutsMenu = new KMenu( menu );
            webShortcutsMenu->setIcon( KIcon( "preferences-web-browser-shortcuts" ) );

            const QString squeezedText = KStringHandler::rsqueeze( searchText, 21 );
            webShortcutsMenu->setTitle( i18n( "Search for '%1' with", squeezedText ) );

            KAction *action = 0;

            foreach( const QString &searchProvider, searchProviders )
            {
                action = new KAction( searchProvider, webShortcutsMenu );
                action->setIcon( KIcon( filterData.iconNameForPreferredSearchProvider( searchProvider ) ) );
                action->setData( filterData.queryForPreferredSearchProvider( searchProvider ) );
                connect( action, SIGNAL(triggered()), this, SLOT(slotHandleWebShortcutAction()) );
                webShortcutsMenu->addAction( action );
            }

            webShortcutsMenu->addSeparator();

            action = new KAction( i18n( "Configure Web Shortcuts..." ), webShortcutsMenu );
            action->setIcon( KIcon( "configure" ) );
            connect( action, SIGNAL(triggered()), this, SLOT(slotConfigureWebShortcuts()) );
            webShortcutsMenu->addAction( action );

            menu->addMenu(webShortcutsMenu);
        }
    }
#endif
}

//BEGIN private SLOTS
void PageView::slotRelayoutPages()
// called by: notifySetup, viewportResizeEvent, slotViewMode, slotContinuousToggled, updateZoom
{
    // set an empty container if we have no pages
    const int pageCount = d->items.count();
    if ( pageCount < 1 )
    {
        return;
    }

    // if viewport was auto-moving, stop it
    if ( d->viewportMoveActive )
    {
        center( d->viewportMoveDest.x(), d->viewportMoveDest.y() );
        d->viewportMoveActive = false;
        d->viewportMoveTimer->stop();
        verticalScrollBar()->setEnabled( true );
        horizontalScrollBar()->setEnabled( true );
    }

    // common iterator used in this method and viewport parameters
    QVector< PageViewItem * >::const_iterator iIt, iEnd = d->items.constEnd();
    int viewportWidth = viewport()->width(),
        viewportHeight = viewport()->height(),
        fullWidth = 0,
        fullHeight = 0;
    QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewportWidth, viewportHeight );

    // handle the 'center first page in row' stuff
    const bool facing = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount > 1;
    const bool facingCentered = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered ||
                               (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount == 1);
    const bool overrideCentering = facingCentered && pageCount < 3;
    const bool centerFirstPage = facingCentered && !overrideCentering;
    const bool facingPages = facing || centerFirstPage;
    const bool centerLastPage = centerFirstPage && pageCount % 2 == 0;
    const bool continuousView = Okular::Settings::viewContinuous();
    const int nCols = overrideCentering ? 1 : viewColumns();

    // set all items geometry and resize contents. handle 'continuous' and 'single' modes separately

    PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage() ) ];

        // Here we find out column's width and row's height to compute a table
        // so we can place widgets 'centered in virtual cells'.
        const int nRows = (int)ceil( (float)(centerFirstPage ? (pageCount + nCols - 1) : pageCount) / (float)nCols );

        int * colWidth = new int[ nCols ],
            * rowHeight = new int[ nRows ],
            cIdx = 0,
            rIdx = 0;
        for ( int i = 0; i < nCols; i++ )
            colWidth[ i ] = viewportWidth / nCols;
        for ( int i = 0; i < nRows; i++ )
            rowHeight[ i ] = 0;
        // handle the 'centering on first row' stuff
        if ( centerFirstPage )
            cIdx += nCols - 1;

        // 1) find the maximum columns width and rows height for a grid in
        // which each page must well-fit inside a cell
        for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt )
        {
            PageViewItem * item = *iIt;
            // update internal page size (leaving a little margin in case of Fit* modes)
            updateItemSize( item, colWidth[ cIdx ] - kcolWidthMargin, viewportHeight - krowHeightMargin );
            // find row's maximum height and column's max width
            if ( item->croppedWidth() + kcolWidthMargin > colWidth[ cIdx ] )
                colWidth[ cIdx ] = item->croppedWidth() + kcolWidthMargin;
            if ( item->croppedHeight() + krowHeightMargin > rowHeight[ rIdx ] )
                rowHeight[ rIdx ] = item->croppedHeight() + krowHeightMargin;
            // handle the 'centering on first row' stuff
            // update col/row indices
            if ( ++cIdx == nCols )
            {
                cIdx = 0;
                rIdx++;
            }
        }

        const int pageRowIdx = ( ( centerFirstPage ? nCols - 1 : 0 ) + currentItem->pageNumber() ) / nCols;

        // 2) compute full size
        for ( int i = 0; i < nCols; i++ )
            fullWidth += colWidth[ i ];
        if ( continuousView )
        {
            for ( int i = 0; i < nRows; i++ )
                fullHeight += rowHeight[ i ];
        }
        else
            fullHeight = rowHeight[ pageRowIdx ];

        // 3) arrange widgets inside cells (and refine fullHeight if needed)
        int insertX = 0,
            insertY = fullHeight < viewportHeight ? ( viewportHeight - fullHeight ) / 2 : 0;
        const int origInsertY = insertY;
        cIdx = 0;
        rIdx = 0;
        if ( centerFirstPage )
        {
            cIdx += nCols - 1;
            for ( int i = 0; i < cIdx; ++i )
                insertX += colWidth[ i ];
        }
        for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt )
        {
            PageViewItem * item = *iIt;
            int cWidth = colWidth[ cIdx ],
                rHeight = rowHeight[ rIdx ];
            if ( continuousView || rIdx == pageRowIdx )
            {
                const bool reallyDoCenterFirst = item->pageNumber() == 0 && centerFirstPage;
                const bool reallyDoCenterLast = item->pageNumber() == pageCount - 1 && centerLastPage;
                int actualX = 0;
                if ( reallyDoCenterFirst || reallyDoCenterLast )
                {
                    // page is centered across entire viewport
                    actualX = (fullWidth - item->croppedWidth()) / 2;
                }
                else if ( facingPages )
                {
                    // page edges 'touch' the center of the viewport
                    actualX = ( (centerFirstPage && item->pageNumber() % 2 == 1) ||
                                (!centerFirstPage && item->pageNumber() % 2 == 0) ) ?
                        (fullWidth / 2) - item->croppedWidth() - 1 : (fullWidth / 2) + 1;
                }
                else
                {
                    // page is centered within its virtual column
                    actualX = insertX + (cWidth - item->croppedWidth()) / 2;
                }
                item->moveTo( actualX,
                              (continuousView ? insertY : origInsertY) + (rHeight - item->croppedHeight()) / 2 );
                item->setVisible( true );
            }
            else
            {
                item->moveTo( 0, 0 );
                item->setVisible( false );
            }
            item->setFormWidgetsVisible( d->m_formsVisible );
            // advance col/row index
            insertX += cWidth;
            if ( ++cIdx == nCols )
            {
                cIdx = 0;
                rIdx++;
                insertX = 0;
                insertY += rHeight;
            }
#ifdef PAGEVIEW_DEBUG
            kWarning() << "updating size for pageno" << item->pageNumber() << "cropped" << item->croppedGeometry() << "uncropped" << item->uncroppedGeometry();
#endif
        }

        delete [] colWidth;
        delete [] rowHeight;

    // 3) reset dirty state
    d->dirtyLayout = false;

    // 4) update scrollview's contents size and recenter view
    bool wasUpdatesEnabled = viewport()->updatesEnabled();
    if ( fullWidth != contentAreaWidth() || fullHeight != contentAreaHeight() )
    {
        const Okular::DocumentViewport vp = d->document->viewport();
        // disable updates and resize the viewportContents
        if ( wasUpdatesEnabled )
            viewport()->setUpdatesEnabled( false );
        resizeContentArea( QSize( fullWidth, fullHeight ) );
        // restore previous viewport if defined and updates enabled
        if ( wasUpdatesEnabled )
        {
            if ( vp.pageNumber >= 0 )
            {
                int prevX = horizontalScrollBar()->value(),
                    prevY = verticalScrollBar()->value();
                const QRect & geometry = d->items[ vp.pageNumber ]->croppedGeometry();
                double nX = vp.rePos.enabled ? normClamp( vp.rePos.normalizedX, 0.5 ) : 0.5,
                       nY = vp.rePos.enabled ? normClamp( vp.rePos.normalizedY, 0.0 ) : 0.0;
                center( geometry.left() + qRound( nX * (double)geometry.width() ),
                        geometry.top() + qRound( nY * (double)geometry.height() ) );
                // center() usually moves the viewport, that requests pixmaps too.
                // if that doesn't happen we have to request them by hand
                if ( prevX == horizontalScrollBar()->value() && prevY == verticalScrollBar()->value() )
                    slotRequestVisiblePixmaps();
            }
            // or else go to center page
            else
                center( fullWidth / 2, 0 );
            viewport()->setUpdatesEnabled( true );
        }
    }

    // 5) update the whole viewport if updated enabled
    if ( wasUpdatesEnabled )
        viewport()->update();
}

void PageView::delayedResizeEvent()
{
    // If we already got here we don't need to execute the timer slot again
    d->delayResizeEventTimer->stop();
    slotRelayoutPages();
    slotRequestVisiblePixmaps();
}

static void slotRequestPreloadPixmap( Okular::DocumentObserver * observer, const PageViewItem * i, const QRect &expandedViewportRect, QLinkedList< Okular::PixmapRequest * > *requestedPixmaps )
{
    Okular::NormalizedRect preRenderRegion;
    const QRect intersectionRect = expandedViewportRect.intersect( i->croppedGeometry() );
    if ( !intersectionRect.isEmpty() )
        preRenderRegion = Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() );

    // request the pixmap if not already present
    if ( !i->page()->hasPixmap( observer, i->uncroppedWidth(), i->uncroppedHeight(), preRenderRegion ) && i->uncroppedWidth() > 0 )
    {
        Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload;
        requestFeatures |= Okular::PixmapRequest::Asynchronous;
        const bool pageHasTilesManager = i->page()->hasTilesManager( observer );
        if ( pageHasTilesManager && !preRenderRegion.isNull() )
        {
            Okular::PixmapRequest * p = new Okular::PixmapRequest( observer, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, requestFeatures );
            requestedPixmaps->push_back( p );

            p->setNormalizedRect( preRenderRegion );
            p->setTile( true );
        }
        else if ( !pageHasTilesManager )
        {
            Okular::PixmapRequest * p = new Okular::PixmapRequest( observer, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, requestFeatures );
            requestedPixmaps->push_back( p );
            p->setNormalizedRect( preRenderRegion );
        }
    }
}

void PageView::slotRequestVisiblePixmaps( int newValue )
{
    // if requests are blocked (because raised by an unwanted event), exit
    if ( d->blockPixmapsRequest || d->viewportMoveActive ||
         ( QApplication::mouseButtons () & Qt::MidButton ) )
        return;

    // precalc view limits for intersecting with page coords inside the loop
    const bool isEvent = newValue != -1 && !d->blockViewport;
    const QRect viewportRect( horizontalScrollBar()->value(),
                              verticalScrollBar()->value(),
                              viewport()->width(), viewport()->height() );
    const QRect viewportRectAtZeroZero( 0, 0, viewport()->width(), viewport()->height() );

    // some variables used to determine the viewport
    int nearPageNumber = -1;
    const double viewportCenterX = (viewportRect.left() + viewportRect.right()) / 2.0;
    const double viewportCenterY = (viewportRect.top() + viewportRect.bottom()) / 2.0;
    double focusedX = 0.5,
           focusedY = 0.0,
           minDistance = -1.0;
    // Margin (in pixels) around the viewport to preload
    const int pixelsToExpand = 512;

    // iterate over all items
    d->visibleItems.clear();
    QLinkedList< Okular::PixmapRequest * > requestedPixmaps;
    QVector< Okular::VisiblePageRect * > visibleRects;
    QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd();
    for ( ; iIt != iEnd; ++iIt )
    {
        PageViewItem * i = *iIt;
        foreach( FormWidgetIface *fwi, i->formWidgets() )
        {
            Okular::NormalizedRect r = fwi->rect();
            fwi->moveTo(
                qRound( i->uncroppedGeometry().left() + i->uncroppedWidth() * r.left ) + 1 - viewportRect.left(),
                qRound( i->uncroppedGeometry().top() + i->uncroppedHeight() * r.top ) + 1 - viewportRect.top() );
        }
        Q_FOREACH ( VideoWidget *vw, i->videoWidgets() )
        {
            const Okular::NormalizedRect r = vw->normGeometry();
            vw->move(
                qRound( i->uncroppedGeometry().left() + i->uncroppedWidth() * r.left ) + 1 - viewportRect.left(),
                qRound( i->uncroppedGeometry().top() + i->uncroppedHeight() * r.top ) + 1 - viewportRect.top() );

            if ( vw->isPlaying() && viewportRectAtZeroZero.intersect( vw->geometry() ).isEmpty() ) {
                vw->stop();
                vw->pageLeft();
            }
        }

        if ( !i->isVisible() )
            continue;
#ifdef PAGEVIEW_DEBUG
        kWarning() << "checking page" << i->pageNumber();
        kWarning().nospace() << "viewportRect is " << viewportRect << ", page item is " << i->croppedGeometry() << " intersect : " << viewportRect.intersects( i->croppedGeometry() );
#endif
        // if the item doesn't intersect the viewport, skip it
        QRect intersectionRect = viewportRect.intersect( i->croppedGeometry() );
        if ( intersectionRect.isEmpty() )
        {
            continue;
        }

        // add the item to the 'visible list'
        d->visibleItems.push_back( i );
        Okular::VisiblePageRect * vItem = new Okular::VisiblePageRect( i->pageNumber(), Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ) );
        visibleRects.push_back( vItem );
#ifdef PAGEVIEW_DEBUG
        kWarning() << "checking for pixmap for page" << i->pageNumber() << "=" << i->page()->hasPixmap( this, i->uncroppedWidth(), i->uncroppedHeight() );
        kWarning() << "checking for text for page" << i->pageNumber() << "=" << i->page()->hasTextPage();
#endif

        Okular::NormalizedRect expandedVisibleRect = vItem->rect;
        if ( i->page()->hasTilesManager( this ) && Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Low )
        {
            double rectMargin = pixelsToExpand/(double)i->uncroppedHeight();
            expandedVisibleRect.left = qMax( 0.0, vItem->rect.left - rectMargin );
            expandedVisibleRect.top = qMax( 0.0, vItem->rect.top - rectMargin );
            expandedVisibleRect.right = qMin( 1.0, vItem->rect.right + rectMargin );
            expandedVisibleRect.bottom = qMin( 1.0, vItem->rect.bottom + rectMargin );
        }

        // if the item has not the right pixmap, add a request for it
        if ( !i->page()->hasPixmap( this, i->uncroppedWidth(), i->uncroppedHeight(), expandedVisibleRect ) )
        {
#ifdef PAGEVIEW_DEBUG
            kWarning() << "rerequesting visible pixmaps for page" << i->pageNumber() << "!";
#endif
            Okular::PixmapRequest * p = new Okular::PixmapRequest( this, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRIO, Okular::PixmapRequest::Asynchronous );
            requestedPixmaps.push_back( p );

            if ( i->page()->hasTilesManager( this ) )
            {
                p->setNormalizedRect( expandedVisibleRect );
                p->setTile( true );
            }
            else
                p->setNormalizedRect( vItem->rect );
        }

        // look for the item closest to viewport center and the relative
        // position between the item and the viewport center
        if ( isEvent )
        {
            const QRect & geometry = i->croppedGeometry();
            // compute distance between item center and viewport center (slightly moved left)
            double distance = hypot( (geometry.left() + geometry.right()) / 2 - (viewportCenterX - 4),
                                     (geometry.top() + geometry.bottom()) / 2 - viewportCenterY );
            if ( distance >= minDistance && nearPageNumber != -1 )
                continue;
            nearPageNumber = i->pageNumber();
            minDistance = distance;
            if ( geometry.height() > 0 && geometry.width() > 0 )
            {
                focusedX = ( viewportCenterX - (double)geometry.left() ) / (double)geometry.width();
                focusedY = ( viewportCenterY - (double)geometry.top() ) / (double)geometry.height();
            }
        }
    }

    // if preloading is enabled, add the pages before and after in preloading
    if ( !d->visibleItems.isEmpty() &&
         Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low )
    {
        // as the requests are done in the order as they appear in the list,
        // request first the next page and then the previous

        int pagesToPreload = viewColumns();

        // if the greedy option is set, preload all pages
        if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy)
            pagesToPreload = d->items.count();

        const QRect expandedViewportRect = viewportRect.adjusted( 0, -pixelsToExpand, 0, pixelsToExpand );

        for( int j = 1; j <= pagesToPreload; j++ )
        {
            // add the page after the 'visible series' in preload
            const int tailRequest = d->visibleItems.last()->pageNumber() + j;
            if ( tailRequest < (int)d->items.count() )
            {
                slotRequestPreloadPixmap( this, d->items[ tailRequest ], expandedViewportRect, &requestedPixmaps );
            }

            // add the page before the 'visible series' in preload
            const int headRequest = d->visibleItems.first()->pageNumber() - j;
            if ( headRequest >= 0 )
            {
                slotRequestPreloadPixmap( this, d->items[ headRequest ], expandedViewportRect, &requestedPixmaps );
            }

            // stop if we've already reached both ends of the document
            if ( headRequest < 0 && tailRequest >= (int)d->items.count() )
                break;
        }
    }

    // send requests to the document
    if ( !requestedPixmaps.isEmpty() )
    {
        d->document->requestPixmaps( requestedPixmaps );
    }
    // if this functions was invoked by viewport events, send update to document
    if ( isEvent && nearPageNumber != -1 )
    {
        // determine the document viewport
        Okular::DocumentViewport newViewport( nearPageNumber );
        newViewport.rePos.enabled = true;
        newViewport.rePos.normalizedX = focusedX;
        newViewport.rePos.normalizedY = focusedY;
        // set the viewport to other observers
        d->document->setViewport( newViewport , this );
    }
    d->document->setVisiblePageRects( visibleRects, this );
}

void PageView::slotMoveViewport()
{
    // converge to viewportMoveDest in 1 second
    int diffTime = d->viewportMoveTime.elapsed();
    if ( diffTime >= 667 || !d->viewportMoveActive )
    {
        center( d->viewportMoveDest.x(), d->viewportMoveDest.y() );
        d->viewportMoveTimer->stop();
        d->viewportMoveActive = false;
        slotRequestVisiblePixmaps();
        verticalScrollBar()->setEnabled( true );
        horizontalScrollBar()->setEnabled( true );
        return;
    }

    // move the viewport smoothly (kmplot: p(x)=1+0.47*(x-1)^3-0.25*(x-1)^4)
    float convergeSpeed = (float)diffTime / 667.0,
          x = ((float)viewport()->width() / 2.0) + horizontalScrollBar()->value(),
          y = ((float)viewport()->height() / 2.0) + verticalScrollBar()->value(),
          diffX = (float)d->viewportMoveDest.x() - x,
          diffY = (float)d->viewportMoveDest.y() - y;
    convergeSpeed *= convergeSpeed * (1.4 - convergeSpeed);
    center( (int)(x + diffX * convergeSpeed),
            (int)(y + diffY * convergeSpeed ) );
}

void PageView::slotAutoScoll()
{
    // the first time create the timer
    if ( !d->autoScrollTimer )
    {
        d->autoScrollTimer = new QTimer( this );
        d->autoScrollTimer->setSingleShot( true );
        connect( d->autoScrollTimer, SIGNAL(timeout()), this, SLOT(slotAutoScoll()) );
    }

    // if scrollIncrement is zero, stop the timer
    if ( !d->scrollIncrement )
    {
        d->autoScrollTimer->stop();
        return;
    }

    // compute delay between timer ticks and scroll amount per tick
    int index = abs( d->scrollIncrement ) - 1;  // 0..9
    const int scrollDelay[10] =  { 200, 100, 50, 30, 20, 30, 25, 20, 30, 20 };
    const int scrollOffset[10] = {   1,   1,  1,  1,  1,  2,  2,  2,  4,  4 };
    d->autoScrollTimer->start( scrollDelay[ index ] );
    int delta = d->scrollIncrement > 0 ? scrollOffset[ index ] : -scrollOffset[ index ];
    verticalScrollBar()->setValue(verticalScrollBar()->value() + delta);
}

void PageView::slotDragScroll()
{
    scrollTo( horizontalScrollBar()->value() + d->dragScrollVector.x(), verticalScrollBar()->value() + d->dragScrollVector.y() );
    QPoint p = contentAreaPosition() + viewport()->mapFromGlobal( QCursor::pos() );
    updateSelection( p );
}

void PageView::slotShowWelcome()
{
    // show initial welcome text
    d->messageWindow->display( i18n( "Welcome" ), QString(), PageViewMessage::Info, 2000 );
}

void PageView::slotShowSizeAllCursor()
{
    setCursor( Qt::SizeAllCursor );
}

void PageView::slotHandleWebShortcutAction()
{
#if KDE_IS_VERSION(4,5,70)
    KAction *action = qobject_cast<KAction*>( sender() );

    if (action)
    {
        KUriFilterData filterData( action->data().toString() );

        if ( KUriFilter::self()->filterSearchUri( filterData, KUriFilter::WebShortcutFilter ) )
        {
            KToolInvocation::invokeBrowser( filterData.uri().url() );
        }
    }
#endif
}

void PageView::slotConfigureWebShortcuts()
{
    KToolInvocation::kdeinitExec( "kcmshell4", QStringList() << "ebrowsing" );
}

void PageView::slotZoom()
{
    if ( !d->aZoom->selectableActionGroup()->isEnabled() )
        return;

    setFocus();
    updateZoom( ZoomFixed );
}

void PageView::slotZoomIn()
{
    updateZoom( ZoomIn );
}

void PageView::slotZoomOut()
{
    updateZoom( ZoomOut );
}

void PageView::slotFitToWidthToggled( bool on )
{
    if ( on ) updateZoom( ZoomFitWidth );
}

void PageView::slotFitToPageToggled( bool on )
{
    if ( on ) updateZoom( ZoomFitPage );
}

void PageView::slotAutoFitToggled( bool on )
{
    if ( on ) updateZoom( ZoomFitAuto );
}

void PageView::slotViewMode( QAction *action )
{
    const int nr = action->data().toInt();
    if ( (int)Okular::Settings::viewMode() != nr )
    {
        Okular::Settings::setViewMode( nr );
        Okular::Settings::self()->writeConfig();
        if ( d->document->pages() > 0 )
            slotRelayoutPages();
    }
}

void PageView::slotContinuousToggled( bool on )
{
    if ( Okular::Settings::viewContinuous() != on )
    {
        Okular::Settings::setViewContinuous( on );
        Okular::Settings::self()->writeConfig();
        if ( d->document->pages() > 0 )
            slotRelayoutPages();
    }
}

void PageView::slotSetMouseNormal()
{
    Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::Browse );
    // hide the messageWindow
    d->messageWindow->hide();
    // reshow the annotator toolbar if hiding was forced (and if it is not already visible)
    if ( d->annotator && d->annotator->hidingWasForced() && d->aToggleAnnotator && !d->aToggleAnnotator->isChecked() )
        d->aToggleAnnotator->trigger();
    // force an update of the cursor
    updateCursor();
    Okular::Settings::self()->writeConfig();
}

void PageView::slotSetMouseZoom()
{
    Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::Zoom );
    // change the text in messageWindow (and show it if hidden)
    d->messageWindow->display( i18n( "Select zooming area. Right-click to zoom out." ), QString(), PageViewMessage::Info, -1 );
    // force hiding of annotator toolbar
    if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() )
    {
        d->aToggleAnnotator->trigger();
        d->annotator->setHidingForced( true );
    }
    // force an update of the cursor
    updateCursor();
    Okular::Settings::self()->writeConfig();
}

void PageView::slotSetMouseMagnifier()
{
    Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::Magnifier );
    d->messageWindow->display( i18n( "Click to see the magnified view." ), QString() );

    // force an update of the cursor
    updateCursor();
    Okular::Settings::self()->writeConfig();
}

void PageView::slotSetMouseSelect()
{
    Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::RectSelect );
    // change the text in messageWindow (and show it if hidden)
    d->messageWindow->display( i18n( "Draw a rectangle around the text/graphics to copy." ), QString(), PageViewMessage::Info, -1 );
    // force hiding of annotator toolbar
    if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() )
    {
        d->aToggleAnnotator->trigger();
        d->annotator->setHidingForced( true );
    }
    // force an update of the cursor
    updateCursor();
    Okular::Settings::self()->writeConfig();
}

void PageView::slotSetMouseTextSelect()
{
    Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::TextSelect );
    // change the text in messageWindow (and show it if hidden)
    d->messageWindow->display( i18n( "Select text" ), QString(), PageViewMessage::Info, -1 );
    // force hiding of annotator toolbar
    if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() )
    {
        d->aToggleAnnotator->trigger();
        d->annotator->setHidingForced( true );
    }
    // force an update of the cursor
    updateCursor();
    Okular::Settings::self()->writeConfig();
}

void PageView::slotSetMouseTableSelect()
{
    Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::TableSelect );
    // change the text in messageWindow (and show it if hidden)
    d->messageWindow->display( i18n(
        "Draw a rectangle around the table, then click near edges to divide up; press Esc to clear."
        ), QString(), PageViewMessage::Info, -1 );
    // force hiding of annotator toolbar
    if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() )
    {
        d->aToggleAnnotator->trigger();
        d->annotator->setHidingForced( true );
    }
    // force an update of the cursor
    updateCursor();
    Okular::Settings::self()->writeConfig();
}

void PageView::slotToggleAnnotator( bool on )
{
    // the 'inHere' trick is needed as the slotSetMouseZoom() calls this
    static bool inHere = false;
    if ( inHere )
        return;
    inHere = true;

    // the annotator can be used in normal mouse mode only, so if asked for it,
    // switch to normal mode
    if ( on && Okular::Settings::mouseMode() != Okular::Settings::EnumMouseMode::Browse )
        d->aMouseNormal->trigger();

    // 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
        if ( userName.isEmpty() )
        {
            bool ok = false;
            userName = KInputDialog::getText(
                           i18n( "Annotations author" ),
                           i18n( "Please insert your name or initials:" ),
                           QString(),
                           &ok );
            if ( !ok )
            {
                d->aToggleAnnotator->trigger();
                inHere = false;
                return;
            }
        }
        // save the name
        Okular::Settings::setIdentityAuthor( userName );
        Okular::Settings::self()->writeConfig();
    }

    // create the annotator object if not present
    if ( !d->annotator )
    {
        d->annotator = new PageViewAnnotator( this, d->document );
        bool allowTools = d->document->pages() > 0 && d->document->isAllowed( Okular::AllowNotes );
        d->annotator->setToolsEnabled( allowTools );
        d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() );
    }

    // initialize/reset annotator (and show/hide toolbar)
    d->annotator->setEnabled( on );
    d->annotator->setHidingForced( false );

    inHere = false;
}

void PageView::slotAutoScrollUp()
{
    if ( d->scrollIncrement < -9 )
        return;
    d->scrollIncrement--;
    slotAutoScoll();
    setFocus();
}

void PageView::slotAutoScrollDown()
{
    if ( d->scrollIncrement > 9 )
        return;
    d->scrollIncrement++;
    slotAutoScoll();
    setFocus();
}

void PageView::slotScrollUp( bool singleStep )
{
    // if in single page mode and at the top of the screen, go to \ page
    if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() > verticalScrollBar()->minimum() )
    {
        if ( singleStep )
            verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub );
        else
            verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepSub );
    }
    else if ( d->document->currentPage() > 0 )
    {
        // more optimized than document->setPrevPage and then move view to bottom
        Okular::DocumentViewport newViewport = d->document->viewport();
        newViewport.pageNumber -= viewColumns();
        if ( newViewport.pageNumber < 0 )
            newViewport.pageNumber = 0;
        newViewport.rePos.enabled = true;
        newViewport.rePos.normalizedY = 1.0;
        d->document->setViewport( newViewport );
    }
}

void PageView::slotScrollDown( bool singleStep )
{
    // if in single page mode and at the bottom of the screen, go to next page
    if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() < verticalScrollBar()->maximum() )
    {
        if ( singleStep )
            verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd );
        else
            verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepAdd );
    }
    else if ( (int)d->document->currentPage() < d->items.count() - 1 )
    {
        // more optimized than document->setNextPage and then move view to top
        Okular::DocumentViewport newViewport = d->document->viewport();
        newViewport.pageNumber += viewColumns();
        if ( newViewport.pageNumber >= (int)d->items.count() )
            newViewport.pageNumber = d->items.count() - 1;
        newViewport.rePos.enabled = true;
        newViewport.rePos.normalizedY = 0.0;
        d->document->setViewport( newViewport );
    }
}

void PageView::slotRotateClockwise()
{
    int id = ( (int)d->document->rotation() + 1 ) % 4;
    d->document->setRotation( id );
}

void PageView::slotRotateCounterClockwise()
{
    int id = ( (int)d->document->rotation() + 3 ) % 4;
    d->document->setRotation( id );
}

void PageView::slotRotateOriginal()
{
    d->document->setRotation( 0 );
}

void PageView::slotPageSizes( int newsize )
{
    if ( newsize < 0 || newsize >= d->document->pageSizes().count() )
        return;

    d->document->setPageSize( d->document->pageSizes().at( newsize ) );
}

void PageView::slotTrimMarginsToggled( bool on )
{
    if ( Okular::Settings::trimMargins() != on )
    {
        Okular::Settings::setTrimMargins( on );
        Okular::Settings::self()->writeConfig();
        if ( d->document->pages() > 0 )
        {
            slotRelayoutPages();
            slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already!
        }
    }
}

void PageView::slotToggleForms()
{
    toggleFormWidgets( !d->m_formsVisible );
}

void PageView::slotFormChanged( int pageNumber )
{
    if ( !d->refreshTimer )
    {
        d->refreshTimer = new QTimer( this );
        d->refreshTimer->setSingleShot( true );
        connect( d->refreshTimer, SIGNAL( timeout() ),
                 this, SLOT( slotRefreshPage() ) );
    }
    d->refreshPage = pageNumber;
    int delay = 0;
    if ( d->m_formsVisible )
    {
        delay = 1000;
    }
    d->refreshTimer->start( delay );
}

void PageView::slotRefreshPage()
{
    const int req = d->refreshPage;
    if ( req < 0 )
        return;
    d->refreshPage = -1;
    QMetaObject::invokeMethod( d->document, "refreshPixmaps", Qt::QueuedConnection,
                               Q_ARG( int, req ) );
}

void PageView::slotSpeakDocument()
{
    QString text;
    QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd();
    for ( ; it < itEnd; ++it )
    {
        Okular::RegularAreaRect * area = textSelectionForItem( *it );
        text.append( (*it)->page()->text( area ) );
        text.append( '\n' );
        delete area;
    }

    d->tts()->say( text );
}

void PageView::slotSpeakCurrentPage()
{
    const int currentPage = d->document->viewport().pageNumber;

    PageViewItem *item = d->items.at( currentPage );
    Okular::RegularAreaRect * area = textSelectionForItem( item );
    const QString text = item->page()->text( area );
    delete area;

    d->tts()->say( text );
}

void PageView::slotStopSpeaks()
{
    if ( !d->m_tts )
        return;

    d->m_tts->stopAllSpeechs();
}

void PageView::slotAction( Okular::Action *action )
{
    d->document->processAction( action );
}

void PageView::externalKeyPressEvent( QKeyEvent *e )
{
    keyPressEvent( e );
}

void PageView::slotProcessMovieAction( const Okular::MovieAction *action )
{
    const Okular::MovieAnnotation *movieAnnotation = action->annotation();
    if ( !movieAnnotation )
        return;

    Okular::Movie *movie = movieAnnotation->movie();
    if ( !movie )
        return;

    const int currentPage = d->document->viewport().pageNumber;

    PageViewItem *item = d->items.at( currentPage );
    if ( !item )
        return;

    VideoWidget *vw = item->videoWidgets().value( movie );
    if ( !vw )
        return;

    vw->show();

    switch ( action->operation() )
    {
        case Okular::MovieAction::Play:
            vw->stop();
            vw->play();
            break;
        case Okular::MovieAction::Stop:
            vw->stop();
            break;
        case Okular::MovieAction::Pause:
            vw->pause();
            break;
        case Okular::MovieAction::Resume:
            vw->play();
            break;
    };
}

void PageView::slotProcessRenditionAction( const Okular::RenditionAction *action )
{
    Okular::Movie *movie = action->movie();
    if ( !movie )
        return;

    const int currentPage = d->document->viewport().pageNumber;

    PageViewItem *item = d->items.at( currentPage );
    if ( !item )
        return;

    VideoWidget *vw = item->videoWidgets().value( movie );
    if ( !vw )
        return;

    if ( action->operation() == Okular::RenditionAction::None )
        return;

    vw->show();

    switch ( action->operation() )
    {
        case Okular::RenditionAction::Play:
            vw->stop();
            vw->play();
            break;
        case Okular::RenditionAction::Stop:
            vw->stop();
            break;
        case Okular::RenditionAction::Pause:
            vw->pause();
            break;
        case Okular::RenditionAction::Resume:
            vw->play();
            break;
    };
}

void PageView::slotToggleChangeColors()
{
    Okular::SettingsCore::setChangeColors( !Okular::SettingsCore::changeColors() );
    Okular::Settings::self()->writeConfig();
    viewport()->update();
}

//END private SLOTS

#include "pageview.moc"

/* kate: replace-tabs on; indent-width 4; */
