pageview.cpp 70.4 KB
Newer Older
1
/***************************************************************************
2
 *   Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>                  *
3
 *   Copyright (C) 2004 by Albert Astals Cid <tsdgeos@terra.es>            *
4
 *                                                                         *
5
 *   With portions of code from kpdf/kpdf_pagewidget.cc by:                *
6 7 8 9 10 11
 *     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>                  *
12 13 14 15 16 17 18
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

19
// qt/kde includes
20
#include <qcursor.h>
21 22
#include <qpainter.h>
#include <qtimer.h>
Enrico Ros's avatar
Enrico Ros committed
23
#include <qdatetime.h>
24
#include <qpushbutton.h>
Enrico Ros's avatar
Enrico Ros committed
25 26
#include <qapplication.h>
#include <qclipboard.h>
27
#include <dcopclient.h>
28 29 30
#include <kiconloader.h>
#include <kurldrag.h>
#include <kaction.h>
Jakub Stachowski's avatar
Jakub Stachowski committed
31
#include <kstdaccel.h>
32
#include <kactioncollection.h>
33
#include <kpopupmenu.h>
34
#include <klocale.h>
35
#include <kfiledialog.h>
36
#include <kimageeffect.h>
37
#include <kimageio.h>
38
#include <kdebug.h>
39

40
// system includes
41
#include <math.h>
42
#include <stdlib.h>
43

44
// local includes
45
#include "pageview.h"
46
#include "pageviewutils.h"
47
#include "pagepainter.h"
48
#include "core/document.h"
49 50 51 52
#include "core/page.h"
#include "core/link.h"
#include "core/generator.h"
#include "conf/settings.h"
53

54 55
#define ROUND(x) (int(x + 0.5))

56 57 58
// definition of searchID for this class
#define PAGEVIEW_SEARCH_ID 2

59 60 61 62
// structure used internally by PageView for data storage
class PageViewPrivate
{
public:
63
    // the document, pageviewItems and the 'visible cache'
64
    KPDFDocument * document;
65
    QValueVector< PageViewItem * > items;
66
    QValueList< PageViewItem * > visibleItems;
67

68
    // view layout (columns and continuous in Settings), zoom and mouse
69 70 71 72
    PageView::ZoomMode zoomMode;
    float zoomFactor;
    PageView::MouseMode mouseMode;
    QPoint mouseGrabPos;
73
    QPoint mousePressPos;
74
    int mouseMidStartY;
75
    bool mouseOnRect;
76
    QRect mouseSelectionRect;
77
    QColor selectionRectColor;
78

Enrico Ros's avatar
Enrico Ros committed
79 80 81 82 83 84 85 86 87 88
    // type ahead find
    bool typeAheadActive;
    QString typeAheadString;
    QTimer * findTimeoutTimer;
    // viewport move
    bool viewportMoveActive;
    QTime viewportMoveTime;
    QPoint viewportMoveDest;
    QTimer * viewportMoveTimer;
    // auto scroll
89
    int scrollIncrement;
Enrico Ros's avatar
Enrico Ros committed
90 91 92
    QTimer * autoScrollTimer;
    // other stuff
    QTimer * delayResizeTimer;
93
    bool dirtyLayout;
Enrico Ros's avatar
Enrico Ros committed
94
    bool blockViewport;                 // prevents changes to viewport
95 96
    bool blockPixmapsRequest;           // prevent pixmap requests
    PageViewMessage * messageWindow;    // in pageviewutils.h
97 98

    // actions
99 100
    KToggleAction * aMouseNormal;
    KToggleAction * aMouseSelect;
101 102 103 104 105 106
    KToggleAction * aMouseEdit;
    KSelectAction * aZoom;
    KToggleAction * aZoomFitWidth;
    KToggleAction * aZoomFitPage;
    KToggleAction * aZoomFitText;
    KToggleAction * aViewTwoPages;
107
    KToggleAction * aViewContinuous;
108
    KAction * aPrevAction;
109 110 111
};


112

113 114 115 116
/* 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)
117
 *  550 - events: mouse, keyboard, drag/drop
118
 *  170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes
119
 *  100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc..
120
 *  other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable,
121
 * and many insignificant stuff like this comment :-)
122 123
 */
PageView::PageView( QWidget *parent, KPDFDocument *document )
124
    : QScrollView( parent, "KPDF::pageView", WStaticContents | WNoAutoErase )
125 126 127 128
{
    // create and initialize private storage structure
    d = new PageViewPrivate();
    d->document = document;
129
    d->zoomMode = ZoomFixed;
130
    d->zoomFactor = 1.0;
131
    d->mouseMode = MouseNormal;
132
    d->mouseMidStartY = -1;
133
    d->mouseOnRect = false;
Enrico Ros's avatar
Enrico Ros committed
134 135 136 137
    d->typeAheadActive = false;
    d->findTimeoutTimer = 0;
    d->viewportMoveActive = false;
    d->viewportMoveTimer = 0;
138
    d->scrollIncrement = 0;
Enrico Ros's avatar
Enrico Ros committed
139 140
    d->autoScrollTimer = 0;
    d->delayResizeTimer = 0;
141
    d->dirtyLayout = false;
142
    d->blockViewport = false;
143
    d->blockPixmapsRequest = false;
144
    d->messageWindow = new PageViewMessage(this);
145
    d->aPrevAction = 0;
146 147 148 149

    // widget setup: setup focus, accept drops and track mouse
    viewport()->setFocusProxy( this );
    viewport()->setFocusPolicy( StrongFocus );
150 151
    //viewport()->setPaletteBackgroundColor( Qt::white );
    viewport()->setBackgroundMode( Qt::NoBackground );
152
    setResizePolicy( Manual );
153
    setAcceptDrops( true );
154
    setDragAutoScroll( false );
155
    viewport()->setMouseTracking( true );
156 157 158

    // conntect the padding of the viewport to pixmaps requests
    connect( this, SIGNAL(contentsMoving(int, int)), this, SLOT(slotRequestVisiblePixmaps(int, int)) );
159

160 161
    // show initial welcome text
    d->messageWindow->display( i18n( "Welcome" ), PageViewMessage::Info, 2000 );
162

163
    // set a corner button to resize the view to the page size
164 165 166 167
//    QPushButton * resizeButton = new QPushButton( viewport() );
//    resizeButton->setPixmap( SmallIcon("crop") );
//    setCornerWidget( resizeButton );
//    resizeButton->setEnabled( false );
168 169 170 171 172 173 174 175
    // connect(...);
}

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

176
void PageView::setupActions( KActionCollection * ac )
177
{
178
    // Zoom actions ( higher scales takes lots of memory! )
179
    d->aZoom = new KSelectAction( i18n( "Zoom" ), "viewmag", 0, this, SLOT( slotZoom() ), ac, "zoom_to" );
180 181
    d->aZoom->setEditable( true );
    updateZoomText();
182 183 184 185 186 187 188 189 190 191 192

    KStdAction::zoomIn( this, SLOT( slotZoomIn() ), ac, "zoom_in" );

    KStdAction::zoomOut( this, SLOT( slotZoomOut() ), ac, "zoom_out" );

    d->aZoomFitWidth = new KToggleAction( i18n("Fit to Page &Width"), "viewmagfit", 0, ac, "zoom_fit_width" );
    connect( d->aZoomFitWidth, SIGNAL( toggled( bool ) ), SLOT( slotFitToWidthToggled( bool ) ) );

    d->aZoomFitPage = new KToggleAction( i18n("Fit to &Page"), "viewmagfit", 0, ac, "zoom_fit_page" );
    connect( d->aZoomFitPage, SIGNAL( toggled( bool ) ), SLOT( slotFitToPageToggled( bool ) ) );

193
    d->aZoomFitText = new KToggleAction( i18n("Fit to &Text"), "viewmagfit", 0, ac, "zoom_fit_text" );
194 195
    connect( d->aZoomFitText, SIGNAL( toggled( bool ) ), SLOT( slotFitToTextToggled( bool ) ) );

196
    // View-Layout actions
197
    d->aViewTwoPages = new KToggleAction( i18n("&Two Pages"), "view_left_right", 0, ac, "view_twopages" );
198
    connect( d->aViewTwoPages, SIGNAL( toggled( bool ) ), SLOT( slotTwoPagesToggled( bool ) ) );
199
    d->aViewTwoPages->setChecked( Settings::viewColumns() > 1 );
200

201 202 203
    d->aViewContinuous = new KToggleAction( i18n("&Continuous"), "view_text", 0, ac, "view_continuous" );
    connect( d->aViewContinuous, SIGNAL( toggled( bool ) ), SLOT( slotContinuousToggled( bool ) ) );
    d->aViewContinuous->setChecked( Settings::viewContinuous() );
204 205

    // Mouse-Mode actions
206 207 208
    d->aMouseNormal = new KRadioAction( i18n("&Normal"), "mouse", 0, this, SLOT( slotSetMouseNormal() ), ac, "mouse_drag" );
    d->aMouseNormal->setExclusiveGroup( "MouseType" );
    d->aMouseNormal->setChecked( true );
209

210
    KToggleAction * mz = new KRadioAction( i18n("&Zoom Tool"), "viewmag", 0, this, SLOT( slotSetMouseZoom() ), ac, "mouse_zoom" );
211 212
    mz->setExclusiveGroup( "MouseType" );

213 214
    d->aMouseSelect = new KRadioAction( i18n("&Select"), "frame_edit", 0, this, SLOT( slotSetMouseSelect() ), ac, "mouse_select" );
    d->aMouseSelect->setExclusiveGroup( "MouseType" );
215

216
/*    d->aMouseEdit = new KRadioAction( i18n("Draw"), "edit", 0, this, SLOT( slotSetMouseDraw() ), ac, "mouse_draw" );
217
    d->aMouseEdit->setExclusiveGroup("MouseType");
218
    d->aMouseEdit->setEnabled( false ); // implement feature before removing this line*/
219 220

    // Other actions
221 222 223 224 225
    KAction * su = new KAction( i18n("Scroll Up"), 0, this, SLOT( slotScrollUp() ), ac, "view_scroll_up" );
    su->setShortcut( "Shift+Up" );

    KAction * sd = new KAction( i18n("Scroll Down"), 0, this, SLOT( slotScrollDown() ), ac, "view_scroll_down" );
    sd->setShortcut( "Shift+Down" );
226 227
}

228 229
bool PageView::canFitPageWidth()
{
230
    return d->zoomMode != ZoomFitWidth;
231 232
}

233
void PageView::fitPageWidth( int /*page*/ )
234
{
235 236
    d->aZoom->setCurrentItem(0);
    slotZoom();
237 238
}

239 240
//BEGIN DocumentObserver inherited methods
void PageView::notifySetup( const QValueVector< KPDFPage * > & pageSet, bool documentChanged )
Enrico Ros's avatar
Enrico Ros committed
241 242
{
    // reuse current pages if nothing new
243
    if ( ( pageSet.count() == d->items.count() ) && !documentChanged )
Enrico Ros's avatar
Enrico Ros committed
244 245 246
    {
        int count = pageSet.count();
        for ( int i = 0; (i < count) && !documentChanged; i++ )
247
            if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() )
Enrico Ros's avatar
Enrico Ros committed
248 249 250 251 252 253
                documentChanged = true;
        if ( !documentChanged )
            return;
    }

    // delete all widgets (one for each page in pageSet)
254
    QValueVector< PageViewItem * >::iterator dIt = d->items.begin(), dEnd = d->items.end();
255 256
    for ( ; dIt != dEnd; ++dIt )
        delete *dIt;
257
    d->items.clear();
258
    d->visibleItems.clear();
259 260 261 262

    // create children widgets
    QValueVector< KPDFPage * >::const_iterator setIt = pageSet.begin(), setEnd = pageSet.end();
    for ( ; setIt != setEnd; ++setIt )
263
        d->items.push_back( new PageViewItem( *setIt ) );
264

265
    // invalidate layout so relayout/repaint will happen on next viewport change
266 267 268 269
    if ( pageSet.count() > 0 )
        d->dirtyLayout = true;
    else
        resizeContents( 0, 0 );
270 271

    // OSD to display pages
272
    if ( documentChanged && pageSet.count() > 0 && Settings::showOSD() )
273
        d->messageWindow->display(
Andrew Coles's avatar
 
Andrew Coles committed
274 275
            i18n(" Loaded a one-page document.",
                 " Loaded a %n-page document.",
276
                 pageSet.count() ),
277
            PageViewMessage::Info, 4000 );
278 279
}

Enrico Ros's avatar
Enrico Ros committed
280
void PageView::notifyViewportChanged( bool smoothMove )
281
{
282 283 284 285 286 287 288 289
    // 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
290
    const DocumentViewport & vp = d->document->viewport();
291
    PageViewItem * item = 0;
292 293
    QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end();
    for ( ; iIt != iEnd; ++iIt )
294
        if ( (*iIt)->pageNumber() == vp.pageNumber )
295
        {
296
            item = *iIt;
297 298
            break;
        }
299 300 301
    if ( !item )
    {
        kdDebug() << "viewport has no matching item!" << endl;
302
        return;
303
    }
304

Enrico Ros's avatar
Enrico Ros committed
305
    // relayout in "Single Pages" mode or if a relayout is pending
306
    d->blockPixmapsRequest = true;
307
    if ( !Settings::viewContinuous() || d->dirtyLayout )
308 309
        slotRelayoutPages();

Enrico Ros's avatar
Enrico Ros committed
310
    // restore viewport center or use default {x-center,v-top} alignment
311
    const QRect & r = item->geometry();
Enrico Ros's avatar
Enrico Ros committed
312 313
    int newCenterX = r.left(),
        newCenterY = r.top();
314
    if ( vp.reCenter.enabled )
315
    {
Enrico Ros's avatar
Enrico Ros committed
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
        newCenterX += (int)( vp.reCenter.normalizedCenterX * (double)r.width() );
        newCenterY += (int)( vp.reCenter.normalizedCenterY * (double)r.height() );
    }
    else
    {
        newCenterX += r.width() / 2;
        newCenterY += visibleHeight() / 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 );
341
    }
342
    else
Enrico Ros's avatar
Enrico Ros committed
343
        center( newCenterX, newCenterY );
344
    d->blockPixmapsRequest = false;
345 346

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

349 350 351
    // enable setViewport calls
    d->blockViewport = false;

352 353 354
    // update zoom text if in a ZoomFit/* zoom mode
    if ( d->zoomMode != ZoomFixed )
        updateZoomText();
355

356
    // since the page has moved below cursor, update it
357
    updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) );
358
}
359

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

366 367
    // iterate over visible items: if page(pageNumber) is one of them, repaint it
    QValueList< PageViewItem * >::iterator iIt = d->visibleItems.begin(), iEnd = d->visibleItems.end();
368 369 370 371 372 373 374
    for ( ; iIt != iEnd; ++iIt )
        if ( (*iIt)->pageNumber() == pageNumber )
        {
            // update item's rectangle plus the little outline
            QRect expandedRect = (*iIt)->geometry();
            expandedRect.addCoords( -1, -1, 3, 3 );
            updateContents( expandedRect );
375

Albert Astals Cid's avatar
Albert Astals Cid committed
376
            // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor
377
            if ( cursor().shape() != Qt::SizeVerCursor )
378
            {
379
                // since the page has been regenerated below cursor, update it
380 381
                updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) );
            }
382 383 384 385
            break;
        }
}

386
void PageView::notifyContentsCleared( int changedFlags )
387
{
388 389 390
    // if pixmaps were cleared, re-ask them
    if ( changedFlags & DocumentObserver::Pixmap )
        slotRequestVisiblePixmaps();
391
}
392 393 394 395 396 397 398 399 400 401 402 403

bool PageView::canUnloadPixmap( int pageNumber )
{
    // if the item is visible, forbid unloading
    QValueList< PageViewItem * >::iterator vIt = d->visibleItems.begin(), vEnd = d->visibleItems.end();
    for ( ; vIt != vEnd; ++vIt )
        if ( (*vIt)->pageNumber() == pageNumber )
            return false;
    // if hidden premit unloading
    return true;
}
//END DocumentObserver inherited methods
404

405
//BEGIN widget events
406
void PageView::viewportPaintEvent( QPaintEvent * pe )
407
{
408
    // create the rect into contents from the clipped screen rect
409 410
    QRect viewportRect = viewport()->rect();
    QRect contentsRect = pe->rect().intersect( viewportRect );
411 412
    contentsRect.moveBy( contentsX(), contentsY() );
    if ( !contentsRect.isValid() )
413
        return;
414

415 416 417 418
    // create the screen painter. a pixel painted ar contentsX,contentsY
    // appears to the top-left corner of the scrollview.
    QPainter screenPainter( viewport(), true );
    screenPainter.translate( -contentsX(), -contentsY() );
419

420 421 422 423 424 425 426 427
    // selectionRect is the normalized mouse selection rect
    QRect selectionRect = d->mouseSelectionRect;
    if ( !selectionRect.isNull() )
        selectionRect = selectionRect.normalize();
    // selectionRectInternal without the border
    QRect selectionRectInternal = selectionRect;
    selectionRectInternal.addCoords( 1, 1, -1, -1 );
    // color for blending
428 429
    QColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ?
                           d->selectionRectColor : Qt::red;
430

431
    // subdivide region into rects
432
    QMemArray<QRect> allRects = pe->region().rects();
433
    uint numRects = allRects.count();
434 435 436

    // preprocess rects area to see if it worths or not using subdivision
    uint summedArea = 0;
437
    for ( uint i = 0; i < numRects; i++ )
438
    {
439 440 441 442 443 444 445
        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.7 * contentsRect.width() * contentsRect.height());
    if ( !useSubdivision )
        numRects = 1;
446 447

    // iterate over the rects (only one loop if not using subdivision)
448 449 450 451 452 453 454 455 456 457
    for ( uint i = 0; i < numRects; i++ )
    {
        if ( useSubdivision )
        {
            // set 'contentsRect' to a part of the sub-divided region
            contentsRect = allRects[i].normalize().intersect( viewportRect );
            contentsRect.moveBy( contentsX(), contentsY() );
            if ( !contentsRect.isValid() )
                continue;
        }
458

459 460 461
        // note: this check will take care of all things requiring alpha blending (not only selection)
        bool wantCompositing = !selectionRect.isNull() && contentsRect.intersects( selectionRect );

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

469
            // 1) Layer 0: paint items and clear bg on unpainted rects
470
            paintItems( &pixmapPainter, contentsRect );
471 472
            // 2) Layer 1: pixmap manipulated areas
            // 3) Layer 2: paint (blend) transparent selection
473 474
            if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
                 !selectionRectInternal.contains( contentsRect ) )
475
            {
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
                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() );
                    copyBlt( &blendedPixmap, 0,0, &doubleBuffer,
                                blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(),
                                blendRect.width(), blendRect.height() );
                    // blend selBlendColor into the background pixmap
                    QImage blendedImage = blendedPixmap.convertToImage();
                    KImageEffect::blend( selBlendColor.dark(140), blendedImage, 0.2 );
                    // copy the blended pixmap back to its place
                    pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedImage );
                }
                // draw border (red if the selection is too small)
                pixmapPainter.setPen( selBlendColor );
                pixmapPainter.drawRect( selectionRect );
494
            }
495
            // 4) Layer 3: overlays
496
            if ( Settings::debugDrawBoundaries() )
497 498 499 500
            {
                pixmapPainter.setPen( Qt::blue );
                pixmapPainter.drawRect( contentsRect );
            }
501

502 503 504 505
            // finish painting and draw contents
            pixmapPainter.end();
            screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer );
        }
506
        else
507
        {
508
            // 1) Layer 0: paint items and clear bg on unpainted rects
509
            paintItems( &screenPainter, contentsRect );
510 511
            // 2) Layer 1: opaque manipulated ares (filled / contours)
            // 3) Layer 2: paint opaque selection
512 513
            if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
                 !selectionRectInternal.contains( contentsRect ) )
514 515
            {
                screenPainter.setPen( palette().active().highlight().dark(110) );
516
                screenPainter.drawRect( selectionRect );
517
            }
518
            // 4) Layer 3: overlays
519
            if ( Settings::debugDrawBoundaries() )
520 521 522 523
            {
                screenPainter.setPen( Qt::red );
                screenPainter.drawRect( contentsRect );
            }
524
        }
525
    }
526 527 528 529 530
}

void PageView::viewportResizeEvent( QResizeEvent * )
{
    // start a timer that will refresh the pixmap after 0.5s
531
    if ( !d->delayResizeTimer )
532
    {
533 534
        d->delayResizeTimer = new QTimer( this );
        connect( d->delayResizeTimer, SIGNAL( timeout() ), this, SLOT( slotRelayoutPages() ) );
535
    }
536
    d->delayResizeTimer->start( 333, true );
537 538 539 540 541
}

void PageView::keyPressEvent( QKeyEvent * e )
{
    e->accept();
Enrico Ros's avatar
Enrico Ros committed
542

Enrico Ros's avatar
Enrico Ros committed
543
    // if performing a selection or dyn zooming, disable keys handling
544
    if ( !d->mouseSelectionRect.isNull() || d->mouseMidStartY != -1 )
Enrico Ros's avatar
Enrico Ros committed
545 546
        return;

Enrico Ros's avatar
Enrico Ros committed
547
    // handle 'find as you type' (based on khtml/khtmlview.cpp)
Enrico Ros's avatar
Enrico Ros committed
548
    if( d->typeAheadActive )
Jakub Stachowski's avatar
Jakub Stachowski committed
549
    {
550
        // backspace: remove a char and search or terminates search
Enrico Ros's avatar
Enrico Ros committed
551
        if( e->key() == Key_BackSpace )
Jakub Stachowski's avatar
Jakub Stachowski committed
552
        {
553
            if( d->typeAheadString.length() > 1 )
Jakub Stachowski's avatar
Jakub Stachowski committed
554
            {
555
                d->typeAheadString = d->typeAheadString.left( d->typeAheadString.length() - 1 );
556
                bool found = d->document->searchText( PAGEVIEW_SEARCH_ID, d->typeAheadString, true, false,
557
                        KPDFDocument::NextMatch, true, qRgb( 128, 255, 128 ), true );
558 559 560
                QString status = found ? i18n("Text found: \"%1\".") : i18n("Text not found: \"%1\".");
                d->messageWindow->display( status.arg(d->typeAheadString.lower()),
                                           found ? PageViewMessage::Find : PageViewMessage::Warning, 4000 );
Enrico Ros's avatar
Enrico Ros committed
561
                d->findTimeoutTimer->start( 3000, true );
Jakub Stachowski's avatar
Jakub Stachowski committed
562 563 564
            }
            else
            {
565
                findAheadStop();
566
                d->document->resetSearch( PAGEVIEW_SEARCH_ID );
Jakub Stachowski's avatar
Jakub Stachowski committed
567 568
            }
        }
569
        // F3: go to next occurrency
Enrico Ros's avatar
Enrico Ros committed
570 571 572
        else if( e->key() == KStdAccel::findNext() )
        {
            // part doesn't get this key event because of the keyboard grab
Enrico Ros's avatar
Enrico Ros committed
573
            d->findTimeoutTimer->stop(); // restore normal operation during possible messagebox is displayed
Enrico Ros's avatar
Enrico Ros committed
574
            releaseKeyboard();
575
            if ( d->document->continueSearch( PAGEVIEW_SEARCH_ID ) )
Enrico Ros's avatar
Enrico Ros committed
576
                d->messageWindow->display( i18n("Text found: \"%1\".").arg(d->typeAheadString.lower()),
577
                                           PageViewMessage::Find, 3000 );
Enrico Ros's avatar
Enrico Ros committed
578
            d->findTimeoutTimer->start( 3000, true );
Enrico Ros's avatar
Enrico Ros committed
579
            grabKeyboard();
Jakub Stachowski's avatar
Jakub Stachowski committed
580
        }
581
        // esc and return: end search
Enrico Ros's avatar
Enrico Ros committed
582
        else if( e->key() == Key_Escape || e->key() == Key_Return )
Jakub Stachowski's avatar
Jakub Stachowski committed
583
        {
584
            findAheadStop();
Jakub Stachowski's avatar
Jakub Stachowski committed
585
        }
586
        // other key: add to text and search
587
        else if( !e->text().isEmpty() )
Jakub Stachowski's avatar
Jakub Stachowski committed
588
        {
Enrico Ros's avatar
Enrico Ros committed
589
            d->typeAheadString += e->text();
590
            bool found = d->document->searchText( PAGEVIEW_SEARCH_ID, d->typeAheadString, false, false,
591
                    KPDFDocument::NextMatch, true, qRgb( 128, 255, 128 ), true );
592 593 594
            QString status = found ? i18n("Text found: \"%1\".") : i18n("Text not found: \"%1\".");
            d->messageWindow->display( status.arg(d->typeAheadString.lower()),
                                       found ? PageViewMessage::Find : PageViewMessage::Warning, 4000 );
Enrico Ros's avatar
Enrico Ros committed
595
            d->findTimeoutTimer->start( 3000, true );
Jakub Stachowski's avatar
Jakub Stachowski committed
596
        }
597
        return;
Jakub Stachowski's avatar
Jakub Stachowski committed
598
    }
Enrico Ros's avatar
Enrico Ros committed
599
    else if( e->key() == '/' && d->document->isOpened() )
Jakub Stachowski's avatar
Jakub Stachowski committed
600
    {
Enrico Ros's avatar
Enrico Ros committed
601 602 603 604 605 606 607 608
        // stop scrolling the page (if doing it)
        if ( d->autoScrollTimer )
        {
            d->scrollIncrement = 0;
            d->autoScrollTimer->stop();
        }
        // start type-adeas search
        d->typeAheadString = QString();
609
        d->messageWindow->display( i18n("Starting -- find text as you type"), PageViewMessage::Find, 3000 );
Enrico Ros's avatar
Enrico Ros committed
610 611
        d->typeAheadActive = true;
        if ( !d->findTimeoutTimer )
Enrico Ros's avatar
Enrico Ros committed
612 613
        {
            // create the timer on demand
Enrico Ros's avatar
Enrico Ros committed
614
            d->findTimeoutTimer = new QTimer( this );
615
            connect( d->findTimeoutTimer, SIGNAL( timeout() ), this, SLOT( findAheadStop() ) );
Enrico Ros's avatar
Enrico Ros committed
616
        }
Enrico Ros's avatar
Enrico Ros committed
617
        d->findTimeoutTimer->start( 3000, true );
Jakub Stachowski's avatar
Jakub Stachowski committed
618 619 620 621
        grabKeyboard();
        return;
    }

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

626 627 628 629
    // move/scroll page by using keys
    switch ( e->key() )
    {
        case Key_Up:
630
        case Key_PageUp:
631
            // if in single page mode and at the top of the screen, go to previous page
632
            if ( Settings::viewContinuous() || verticalScrollBar()->value() > verticalScrollBar()->minValue() )
633 634 635 636 637 638
            {
                if ( e->key() == Key_Up )
                    verticalScrollBar()->subtractLine();
                else
                    verticalScrollBar()->subtractPage();
            }
639 640 641 642 643 644 645 646 647
            else if ( d->document->currentPage() > 0 )
            {
                // more optimized than document->setPrevPage and then move view to bottom
                DocumentViewport newViewport = d->document->viewport();
                newViewport.pageNumber -= 1;
                newViewport.reCenter.enabled = true;
                newViewport.reCenter.normalizedCenterY = 1.0;
                d->document->setViewport( newViewport );
            }
648 649
            break;
        case Key_Down:
650
        case Key_PageDown:
651
            // if in single page mode and at the bottom of the screen, go to next page
652
            if ( Settings::viewContinuous() || verticalScrollBar()->value() < verticalScrollBar()->maxValue() )
653 654 655 656 657 658
            {
                if ( e->key() == Key_Down )
                    verticalScrollBar()->addLine();
                else
                    verticalScrollBar()->addPage();
            }
659 660 661 662 663 664 665 666 667
            else if ( d->document->currentPage() < d->items.count() - 1 )
            {
                // more optmized than document->setNextPage and then move view to top
                DocumentViewport newViewport = d->document->viewport();
                newViewport.pageNumber += 1;
                newViewport.reCenter.enabled = true;
                newViewport.reCenter.normalizedCenterY = 0.0;
                d->document->setViewport( newViewport );
            }
668 669 670 671 672 673 674 675 676
            break;
        case Key_Left:
            horizontalScrollBar()->subtractLine();
            break;
        case Key_Right:
            horizontalScrollBar()->addLine();
            break;
        case Key_Shift:
        case Key_Control:
Enrico Ros's avatar
Enrico Ros committed
677
            if ( d->autoScrollTimer )
678
            {
Enrico Ros's avatar
Enrico Ros committed
679 680
                if ( d->autoScrollTimer->isActive() )
                    d->autoScrollTimer->stop();
681 682 683 684 685 686 687 688 689
                else
                    slotAutoScoll();
                return;
            }
        default:
            e->ignore();
            return;
    }
    // if a known key has been pressed, stop scrolling the page
Enrico Ros's avatar
Enrico Ros committed
690
    if ( d->autoScrollTimer )
691 692
    {
        d->scrollIncrement = 0;
Enrico Ros's avatar
Enrico Ros committed
693
        d->autoScrollTimer->stop();
694 695 696 697 698
    }
}

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

Enrico Ros's avatar
Enrico Ros committed
703 704 705 706
    // don't perform any mouse action when viewport is autoscrolling
    if ( d->viewportMoveActive )
        return;

707 708 709 710 711 712 713
    // if holding mouse mid button, perform zoom
    if ( (e->state() & MidButton) && d->mouseMidStartY > 0 )
    {
        int deltaY = d->mouseMidStartY - e->globalPos().y();
        d->mouseMidStartY = e->globalPos().y();
        d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) );
        updateZoom( ZoomRefreshCurrent );
714 715
        // uncomment following line to force a complete redraw
        viewport()->repaint( false );
716 717
        return;
    }
718

719 720
    bool leftButton = e->state() & LeftButton,
         rightButton = e->state() & RightButton;
721 722 723 724 725 726 727 728 729 730 731 732 733 734
    switch ( d->mouseMode )
    {
        case MouseNormal:
            if ( leftButton )
            {
                // drag page
                if ( !d->mouseGrabPos.isNull() )
                {
                    // scroll page by position increment
                    QPoint delta = d->mouseGrabPos - e->globalPos();
                    scrollBy( delta.x(), delta.y() );
                    d->mouseGrabPos = e->globalPos();
                }
            }
735
            else if ( rightButton && !d->mousePressPos.isNull() )
736
            {
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
                // 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->activate();
                    QColor selColor = palette().active().highlight().light( 120 );
                    selectionStart( e->x() + deltaX, e->y() + deltaY, selColor, false );
                    selectionEndPoint( e->x(), e->y() );
                    break;
                }
            }
            else
            {
                // only hovering the page, so update the cursor
753
                updateCursor( e->pos() );
Enrico Ros's avatar
Enrico Ros committed
754
            }
755 756
            break;

757
        case MouseZoom:
758
        case MouseSelect:
759 760
            // set second corner of selection
            if ( (leftButton || d->aPrevAction) && !d->mouseSelectionRect.isNull() )
761
                selectionEndPoint( e->x(), e->y() );
762 763 764 765 766 767 768
            break;

        case MouseEdit:      // ? update graphics ?
            break;
    }
}

769 770
void PageView::contentsMousePressEvent( QMouseEvent * e )
{
771 772 773 774 775
    // 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
Enrico Ros's avatar
Enrico Ros committed
776 777
    if ( !d->mouseSelectionRect.isNull() || d->mouseMidStartY != -1 ||
         d->viewportMoveActive )
778 779
        return;

Enrico Ros's avatar
Enrico Ros committed
780 781 782 783 784 785 786
    // if the page is scrolling, stop it
    if ( d->autoScrollTimer )
    {
        d->scrollIncrement = 0;
        d->autoScrollTimer->stop();
    }

787
    // if pressing mid mouse button while not doing other things, begin 'comtinous zoom' mode
788
    if ( e->button() & MidButton )
789 790 791 792 793
    {
        d->mouseMidStartY = e->globalPos().y();
        setCursor( sizeVerCursor );
        return;
    }
794

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

798
    // handle mode dependant mouse press actions
799 800
    bool leftButton = e->button() & LeftButton,
         rightButton = e->button() & RightButton;
801 802
    switch ( d->mouseMode )
    {
803
        case MouseNormal:   // drag start / click / link following
804
            if ( leftButton )
805
            {
806
                d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mousePressPos;
807
                if ( !d->mouseOnRect )
808
                    setCursor( sizeAllCursor );