pageview.cpp 61.8 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 23
#include <qpainter.h>
#include <qtimer.h>
#include <qpushbutton.h>
Enrico Ros's avatar
Enrico Ros committed
24 25
#include <qapplication.h>
#include <qclipboard.h>
26 27 28
#include <kiconloader.h>
#include <kurldrag.h>
#include <kaction.h>
Jakub Stachowski's avatar
Jakub Stachowski committed
29
#include <kstdaccel.h>
30
#include <kactioncollection.h>
31
#include <kpopupmenu.h>
32
#include <klocale.h>
33
#include <kfiledialog.h>
34
#include <kimageeffect.h>
35
#include <kimageio.h>
36
#include <kdebug.h>
37

38
// system includes
39
#include <math.h>
40
#include <stdlib.h>
41

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

51 52
#define ROUND(x) (int(x + 0.5))

53 54 55 56
// structure used internally by PageView for data storage
class PageViewPrivate
{
public:
57
    // the document, pageviewItems and the 'visible cache'
58
    KPDFDocument * document;
59
    QValueVector< PageViewItem * > items;
60
    QValueList< PageViewItem * > visibleItems;
61

62
    // view layout (columns and continous in Settings), zoom and mouse
63 64 65 66
    PageView::ZoomMode zoomMode;
    float zoomFactor;
    PageView::MouseMode mouseMode;
    QPoint mouseGrabPos;
67
    QPoint mouseStartPos;
68
    int mouseMidStartY;
69
    bool mouseOnRect;
70
    QRect mouseSelectionRect;
71 72

    // other stuff
73 74
    QTimer * delayTimer;
    QTimer * scrollTimer;
Jakub Stachowski's avatar
Jakub Stachowski committed
75
    QTimer * findTimer;
76
    int scrollIncrement;
77
    bool dirtyLayout;
Jakub Stachowski's avatar
Jakub Stachowski committed
78 79
    bool typeAheadActivated;
    QString findString;
80
    bool blockViewport;
81
    PageViewMessage * messageWindow;    //in pageviewutils.h
82 83

    // actions
84 85 86 87 88 89 90
    KToggleAction * aMouseEdit;
    KSelectAction * aZoom;
    KToggleAction * aZoomFitWidth;
    KToggleAction * aZoomFitPage;
    KToggleAction * aZoomFitText;
    KToggleAction * aViewTwoPages;
    KToggleAction * aViewContinous;
91 92 93
};


94

95 96 97 98
/* 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)
99
 *  550 - events: mouse, keyboard, drag/drop
100 101
 *  170 - slotRelayoutPages: set contents of the scrollview on continous/single modes
 *  100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc..
102
 *  other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable,
103
 * and many insignificant stuff like this comment :-)
104 105
 */
PageView::PageView( QWidget *parent, KPDFDocument *document )
106
    : QScrollView( parent, "KPDF::pageView", WStaticContents | WNoAutoErase )
107 108 109 110
{
    // create and initialize private storage structure
    d = new PageViewPrivate();
    d->document = document;
111
    d->zoomMode = ZoomFixed;
112
    d->zoomFactor = 1.0;
113
    d->mouseMode = MouseNormal;
114
    d->mouseMidStartY = -1;
115
    d->mouseOnRect = false;
116
    d->delayTimer = 0;
117
    d->scrollTimer = 0;
Enrico Ros's avatar
Enrico Ros committed
118
    d->findTimer = 0;
Jakub Stachowski's avatar
Jakub Stachowski committed
119
    d->typeAheadActivated = false;
120
    d->scrollIncrement = 0;
121
    d->dirtyLayout = false;
122
    d->blockViewport = false;
123
    d->messageWindow = new PageViewMessage(this);
124 125 126 127

    // widget setup: setup focus, accept drops and track mouse
    viewport()->setFocusProxy( this );
    viewport()->setFocusPolicy( StrongFocus );
128 129
    //viewport()->setPaletteBackgroundColor( Qt::white );
    viewport()->setBackgroundMode( Qt::NoBackground );
130
    setResizePolicy( Manual );
131
    setAcceptDrops( true );
132
    setDragAutoScroll( false );
133
    viewport()->setMouseTracking( true );
134 135 136

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

138 139
    // show initial welcome text
    d->messageWindow->display( i18n( "Welcome" ), PageViewMessage::Info, 2000 );
140

141
    // set a corner button to resize the view to the page size
142 143 144 145
//    QPushButton * resizeButton = new QPushButton( viewport() );
//    resizeButton->setPixmap( SmallIcon("crop") );
//    setCornerWidget( resizeButton );
//    resizeButton->setEnabled( false );
146 147 148 149 150 151 152 153
    // connect(...);
}

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

154
void PageView::setupActions( KActionCollection * ac )
155
{
156
    // Zoom actions ( higher scales takes lots of memory! )
157
    d->aZoom = new KSelectAction( i18n( "Zoom" ), "viewmag", 0, this, SLOT( slotZoom() ), ac, "zoom_to" );
158 159
    d->aZoom->setEditable( true );
    updateZoomText();
160 161 162 163 164 165 166 167 168 169 170

    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 ) ) );

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

174
    // View-Layout actions
175
    d->aViewTwoPages = new KToggleAction( i18n("&Two Pages"), "view_left_right", 0, ac, "view_twopages" );
176
    connect( d->aViewTwoPages, SIGNAL( toggled( bool ) ), SLOT( slotTwoPagesToggled( bool ) ) );
177
    d->aViewTwoPages->setChecked( Settings::viewColumns() > 1 );
178

179
    d->aViewContinous = new KToggleAction( i18n("&Continous"), "view_text", 0, ac, "view_continous" );
180
    connect( d->aViewContinous, SIGNAL( toggled( bool ) ), SLOT( slotContinousToggled( bool ) ) );
181
    d->aViewContinous->setChecked( Settings::viewContinous() );
182 183

    // Mouse-Mode actions
184
    KToggleAction * mn = new KRadioAction( i18n("&Normal"), "mouse", 0, this, SLOT( slotSetMouseNormal() ), ac, "mouse_drag" );
185
    mn->setExclusiveGroup( "MouseType" );
186 187
    mn->setChecked( true );

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

191
    KToggleAction * mst = new KRadioAction( i18n("&Select"), "frame_edit", 0, this, SLOT( slotSetMouseSelect() ), ac, "mouse_select" );
192 193
    mst->setExclusiveGroup( "MouseType" );

194 195 196
    d->aMouseEdit = new KRadioAction( i18n("Draw"), "edit", 0, this, SLOT( slotSetMouseDraw() ), ac, "mouse_draw" );
    d->aMouseEdit->setExclusiveGroup("MouseType");
    d->aMouseEdit->setEnabled( false ); // implement feature before removing this line
197 198

    // Other actions
199 200 201 202 203
    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" );
204 205
}

206 207 208 209 210
bool PageView::canFitPageWidth()
{
    return Settings::viewColumns() != 1 || d->zoomMode != ZoomFitWidth;
}

211
void PageView::fitPageWidth( int page )
212
{
213
    // zoom: Fit Width, columns: 1. setActions + relayout + setPage + update
214 215 216 217 218 219 220 221 222
    d->zoomMode = ZoomFitWidth;
    Settings::setViewColumns( 1 );
    d->aZoomFitWidth->setChecked( true );
    d->aZoomFitPage->setChecked( false );
    d->aZoomFitText->setChecked( false );
    d->aViewTwoPages->setChecked( false );
    viewport()->setUpdatesEnabled( false );
    slotRelayoutPages();
    viewport()->setUpdatesEnabled( true );
223
    d->document->setViewportPage( page );
224
    updateZoomText();
225 226
}

227

228 229
//BEGIN DocumentObserver inherited methods
void PageView::notifySetup( const QValueVector< KPDFPage * > & pageSet, bool documentChanged )
Enrico Ros's avatar
Enrico Ros committed
230 231
{
    // reuse current pages if nothing new
232
    if ( ( pageSet.count() == d->items.count() ) && !documentChanged )
Enrico Ros's avatar
Enrico Ros committed
233 234 235
    {
        int count = pageSet.count();
        for ( int i = 0; (i < count) && !documentChanged; i++ )
236
            if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() )
Enrico Ros's avatar
Enrico Ros committed
237 238 239 240 241 242
                documentChanged = true;
        if ( !documentChanged )
            return;
    }

    // delete all widgets (one for each page in pageSet)
243
    QValueVector< PageViewItem * >::iterator dIt = d->items.begin(), dEnd = d->items.end();
244 245
    for ( ; dIt != dEnd; ++dIt )
        delete *dIt;
246
    d->items.clear();
247
    d->visibleItems.clear();
248 249 250 251

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

254
    // invalidate layout so relayout/repaint will happen on next viewport change
255
    d->dirtyLayout = true;
256 257

    // OSD to display pages
258
    if ( documentChanged && pageSet.count() > 0 && Settings::showOSD() )
259
        d->messageWindow->display(
Andrew Coles's avatar
 
Andrew Coles committed
260 261
            i18n(" Loaded a one-page document.",
                 " Loaded a %n-page document.",
262
                 pageSet.count() ),
263
            PageViewMessage::Info, 4000 );
264 265
}

266
void PageView::notifyViewportChanged()
267
{
268 269 270 271 272 273 274 275 276 277
    // 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 DocumentViewport & viewport = d->document->viewport();
    PageViewItem * item = 0;
278 279
    QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end();
    for ( ; iIt != iEnd; ++iIt )
280
        if ( (*iIt)->pageNumber() == viewport.pageNumber )
281
        {
282
            item = *iIt;
283 284
            break;
        }
285 286 287
    if ( !item )
    {
        kdDebug() << "viewport has no matching item!" << endl;
288
        return;
289
    }
290

Enrico Ros's avatar
Enrico Ros committed
291
    // relayout in "Single Pages" mode or if a relayout is pending
292
    if ( !Settings::viewContinous() || d->dirtyLayout )
293 294
        slotRelayoutPages();

295 296 297
    // restore viewport or use default x-center, v-top alignment
    const QRect & r = item->geometry();
    if ( viewport.reCenter.enabled )
298
    {
299 300 301
        int xCenter = (int)( viewport.reCenter.normalizedCenterX * (float)r.width() );
        int yCenter = (int)( viewport.reCenter.normalizedCenterY * (float)r.height() );
        center( r.left() + xCenter, r.top() + yCenter );
302
    }
303 304 305 306
    else
        center( r.left() + r.width() / 2, r.top() + visibleHeight() / 2 - 10 );

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

309 310 311
    // enable setViewport calls
    d->blockViewport = false;

312 313 314
    // update zoom text if in a ZoomFit/* zoom mode
    if ( d->zoomMode != ZoomFixed )
        updateZoomText();
315

316
    // since the page has moved below cursor, update it
317
    updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) );
318
}
319

320
void PageView::notifyPageChanged( int pageNumber, int /*changedFlags*/ )
321
{
322 323 324
    // only handle pixmap changed notifies (the only defined for now)
    //if ( !(changedFlags & DocumentObserver::Pixmap) )
    //    return;
325

326 327
    // iterate over visible items: if page(pageNumber) is one of them, repaint it
    QValueList< PageViewItem * >::iterator iIt = d->visibleItems.begin(), iEnd = d->visibleItems.end();
328 329 330 331 332 333 334
    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 );
335

Albert Astals Cid's avatar
Albert Astals Cid committed
336
            // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor
337
            if ( cursor().shape() != Qt::SizeVerCursor )
338
            {
339
                // since the page has been regenerated below cursor, update it
340 341
                updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) );
            }
342 343 344 345
            break;
        }
}

346
void PageView::notifyContentsCleared( int changedFlags )
347
{
348 349 350
    // if pixmaps were cleared, re-ask them
    if ( changedFlags & DocumentObserver::Pixmap )
        slotRequestVisiblePixmaps();
351
}
352 353 354 355 356 357 358 359 360 361 362 363

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
364

365
//BEGIN widget events
366
void PageView::viewportPaintEvent( QPaintEvent * pe )
367
{
368
    // create the rect into contents from the clipped screen rect
369 370
    QRect viewportRect = viewport()->rect();
    QRect contentsRect = pe->rect().intersect( viewportRect );
371 372
    contentsRect.moveBy( contentsX(), contentsY() );
    if ( !contentsRect.isValid() )
373
        return;
374

375 376 377 378
    // 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() );
379

380 381 382 383 384 385 386 387 388 389 390
    // 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
    QColor selBlendColor = (selectionRect.width() > 20 || selectionRect.height() > 20) ?
                           palette().active().highlight() : Qt::red;

391
    // subdivide region into rects
392
    QMemArray<QRect> allRects = pe->region().rects();
393
    uint numRects = allRects.count();
394 395 396

    // preprocess rects area to see if it worths or not using subdivision
    uint summedArea = 0;
397
    for ( uint i = 0; i < numRects; i++ )
398
    {
399 400 401 402 403 404 405
        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;
406 407

    // iterate over the rects (only one loop if not using subdivision)
408 409 410 411 412 413 414 415 416 417
    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;
        }
418

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

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

429
            // 1) Layer 0: paint items and clear bg on unpainted rects
430
            paintItems( &pixmapPainter, contentsRect );
431 432
            // 2) Layer 1: pixmap manipulated areas
            // 3) Layer 2: paint (blend) transparent selection
433 434
            if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
                 !selectionRectInternal.contains( contentsRect ) )
435
            {
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
                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 );
454
            }
455
            // 4) Layer 3: overlays
456
            if ( Settings::debugDrawBoundaries() )
457 458 459 460
            {
                pixmapPainter.setPen( Qt::blue );
                pixmapPainter.drawRect( contentsRect );
            }
461

462 463 464 465
            // finish painting and draw contents
            pixmapPainter.end();
            screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer );
        }
466
        else
467
        {
468
            // 1) Layer 0: paint items and clear bg on unpainted rects
469
            paintItems( &screenPainter, contentsRect );
470 471
            // 2) Layer 1: opaque manipulated ares (filled / contours)
            // 3) Layer 2: paint opaque selection
472 473
            if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
                 !selectionRectInternal.contains( contentsRect ) )
474 475
            {
                screenPainter.setPen( palette().active().highlight().dark(110) );
476
                screenPainter.drawRect( selectionRect );
477
            }
478
            // 4) Layer 3: overlays
479
            if ( Settings::debugDrawBoundaries() )
480 481 482 483
            {
                screenPainter.setPen( Qt::red );
                screenPainter.drawRect( contentsRect );
            }
484
        }
485
    }
486 487 488 489 490 491 492 493 494 495
}

void PageView::viewportResizeEvent( QResizeEvent * )
{
    // start a timer that will refresh the pixmap after 0.5s
    if ( !d->delayTimer )
    {
        d->delayTimer = new QTimer( this );
        connect( d->delayTimer, SIGNAL( timeout() ), this, SLOT( slotRelayoutPages() ) );
    }
496
    d->delayTimer->start( 333, true );
497 498 499 500 501
}

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

    // handle 'find as you type' (based on khtml/khtmlview.cpp)
    if( d->typeAheadActivated )
Jakub Stachowski's avatar
Jakub Stachowski committed
505
    {
Enrico Ros's avatar
Enrico Ros committed
506
        if( e->key() == Key_BackSpace )
Jakub Stachowski's avatar
Jakub Stachowski committed
507
        {
Enrico Ros's avatar
Enrico Ros committed
508 509
            d->findString = d->findString.left( d->findString.length() - 1 );
            if( !d->findString.isEmpty() )
Jakub Stachowski's avatar
Jakub Stachowski committed
510
            {
Enrico Ros's avatar
Enrico Ros committed
511 512
                findAhead( false );
                d->findTimer->start( 3000, true );
Jakub Stachowski's avatar
Jakub Stachowski committed
513 514 515 516
            }
            else
            {
                findTimeout();
Enrico Ros's avatar
Enrico Ros committed
517
                d->document->unHilightPages( false );
Jakub Stachowski's avatar
Jakub Stachowski committed
518 519 520
            }
            return;
        }
Enrico Ros's avatar
Enrico Ros committed
521 522 523
        else if( e->key() == KStdAccel::findNext() )
        {
            // part doesn't get this key event because of the keyboard grab
Jakub Stachowski's avatar
Jakub Stachowski committed
524
            d->findTimer->stop(); // restore normal operation during possible messagebox is displayed
Enrico Ros's avatar
Enrico Ros committed
525 526 527 528 529 530
            releaseKeyboard();
            if ( d->document->findText() )
                d->messageWindow->display( i18n("Text found: \"%1\".").arg(d->findString.lower()),
                                           PageViewMessage::Info, 3000 );
            d->findTimer->start( 3000, true );
            grabKeyboard();
Jakub Stachowski's avatar
Jakub Stachowski committed
531 532
            return;
        }
Enrico Ros's avatar
Enrico Ros committed
533
        else if( e->key() == Key_Escape || e->key() == Key_Return )
Jakub Stachowski's avatar
Jakub Stachowski committed
534 535 536 537
        {
            findTimeout();
            return;
        }
Enrico Ros's avatar
Enrico Ros committed
538
        else if( e->text().isEmpty() == false )
Jakub Stachowski's avatar
Jakub Stachowski committed
539 540
        {
            d->findString += e->text();
Enrico Ros's avatar
Enrico Ros committed
541 542
            findAhead( true );
            d->findTimer->start( 3000, true );
Jakub Stachowski's avatar
Jakub Stachowski committed
543 544 545
            return;
        }
    }
Enrico Ros's avatar
Enrico Ros committed
546
    else if( e->key() == '/' && d->document->isOpened() )
Jakub Stachowski's avatar
Jakub Stachowski committed
547
    {
Enrico Ros's avatar
Enrico Ros committed
548
        d->findString = QString();
Jakub Stachowski's avatar
Jakub Stachowski committed
549 550
        d->messageWindow->display(i18n("Starting -- find text as you type"), PageViewMessage::Info, 3000);
        d->typeAheadActivated = true;
Enrico Ros's avatar
Enrico Ros committed
551 552 553 554 555 556 557
        if ( !d->findTimer )
        {
            // create the timer on demand
            d->findTimer = new QTimer( this );
            connect( d->findTimer, SIGNAL(timeout()), this, SLOT(findTimeout()) );
        }
        d->findTimer->start( 3000, true );
Jakub Stachowski's avatar
Jakub Stachowski committed
558 559 560 561
        grabKeyboard();
        return;
    }

562 563 564 565
    // move/scroll page by using keys
    switch ( e->key() )
    {
        case Key_Up:
566
        case Key_PageUp:
567
            // if in single page mode and at the top of the screen, go to previous page
568 569 570 571 572 573 574
            if ( Settings::viewContinous() || verticalScrollBar()->value() > verticalScrollBar()->minValue() )
            {
                if ( e->key() == Key_Up )
                    verticalScrollBar()->subtractLine();
                else
                    verticalScrollBar()->subtractPage();
            }
575 576 577 578 579 580 581 582 583
            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 );
            }
584 585
            break;
        case Key_Down:
586
        case Key_PageDown:
587
            // if in single page mode and at the bottom of the screen, go to next page
588 589 590 591 592 593 594
            if ( Settings::viewContinous() || verticalScrollBar()->value() < verticalScrollBar()->maxValue() )
            {
                if ( e->key() == Key_Down )
                    verticalScrollBar()->addLine();
                else
                    verticalScrollBar()->addPage();
            }
595 596 597 598 599 600 601 602 603
            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 );
            }
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
            break;
        case Key_Left:
            horizontalScrollBar()->subtractLine();
            break;
        case Key_Right:
            horizontalScrollBar()->addLine();
            break;
        case Key_Shift:
        case Key_Control:
            if ( d->scrollTimer )
            {
                if ( d->scrollTimer->isActive() )
                    d->scrollTimer->stop();
                else
                    slotAutoScoll();
                return;
            }
        default:
            e->ignore();
            return;
    }
    // if a known key has been pressed, stop scrolling the page
    if ( d->scrollTimer )
    {
        d->scrollIncrement = 0;
        d->scrollTimer->stop();
    }
}

Jakub Stachowski's avatar
Jakub Stachowski committed
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
void PageView::findTimeout()
{
    d->typeAheadActivated = false;
    d->findString = "";
    d->messageWindow->display(i18n("Find stopped."),PageViewMessage::Info,1000);
    releaseKeyboard();
}

void PageView::findAhead(bool increase)
{
    if (!increase) 
        d->document->setViewportPage(0);
    QString status;
    d->document->unHilightPages(false);
    if(d->document->findText(d->findString, false, true))
        status = i18n("Text found: \"%1\".");
    else 
        status = i18n("Text not found: \"%1\".");
    d->messageWindow->display(status.arg(d->findString.lower()), PageViewMessage::Info, 4000);
}


655 656
void PageView::contentsMouseMoveEvent( QMouseEvent * e )
{
657 658 659 660 661 662 663
    // 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 );
664 665
        // uncomment following line to force a complete redraw
        viewport()->repaint( false );
666 667
        return;
    }
668

669
    bool leftButton = e->state() & LeftButton;
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
    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();
                    // if the page was scrolling, stop it
                    if ( d->scrollTimer )
                    {
                        d->scrollIncrement = 0;
                        d->scrollTimer->stop();
                    }
                }
            }
690
            else // only hovering the page
691
            {
692
                updateCursor( e->pos() );
Enrico Ros's avatar
Enrico Ros committed
693
            }
694 695
            break;

696
        case MouseZoom:
697
        case MouseSelect:
698
            // set second corner of selection in selection pageItem
699 700
            if ( leftButton && !d->mouseSelectionRect.isNull() )
                selectionEndPoint( e->x(), e->y() );
701 702 703 704 705 706 707
            break;

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

708 709
void PageView::contentsMousePressEvent( QMouseEvent * e )
{
710 711 712 713 714 715 716
    // if pressing mid mouse button while not doing other things, begin 'comtinous zoom' mode
    if ( (e->button() & MidButton) && d->mouseSelectionRect.isNull() )
    {
        d->mouseMidStartY = e->globalPos().y();
        setCursor( sizeVerCursor );
        return;
    }
717 718

    // handle mode dependant mouse press actions
719
    bool leftButton = e->button() & LeftButton;
720 721
    switch ( d->mouseMode )
    {
722 723
        case MouseNormal:    // drag start / click / link following
            if ( leftButton )
724
            {
725
                d->mouseStartPos = e->globalPos();
726 727
                d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mouseStartPos;
                if ( !d->mouseOnRect )
728
                    setCursor( sizeAllCursor );
729
            }
730
            break;
731

732
        case MouseZoom:
733
        case MouseSelect:   // set first corner of the selection rect
734 735 736 737
            if ( leftButton )
                selectionStart( e->x(), e->y(), false );
            break;

738 739
        case MouseEdit:      // ? place the beginning of [tool] ?
            break;
740 741 742
    }
}

743
void PageView::contentsMouseReleaseEvent( QMouseEvent * e )
744
{
745 746 747 748
    // handle mode indepent mid buttom zoom
    if ( (e->state() & MidButton) && d->mouseMidStartY > 0 )
    {
        d->mouseMidStartY = -1;
749 750
        // while drag-zooming we could have gone over a link
        updateCursor( e->pos() );
751 752 753
        return;
    }

754 755
    bool leftButton = e->button() & LeftButton,
         rightButton = e->button() & RightButton;
756 757
    switch ( d->mouseMode )
    {
758
        case MouseNormal:{  // do Follow Link or Display RMB
759 760 761
            // return the cursor to its normal state after dragging
            if (cursor().shape() == Qt::SizeAllCursor) updateCursor( e->pos() );

Albert Astals Cid's avatar
Albert Astals Cid committed
762 763
            PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );

764 765 766
            // avoid the situation in where you click on a "row" that has a link but you are not over it
            // drag a bit and move the mouse left to place it over the link while dragging
            // release the button and BOOM you get the link followed
Albert Astals Cid's avatar
Albert Astals Cid committed
767
            if ( leftButton && pageItem && d->mouseStartPos == e->globalPos())
768
            {
769 770 771 772 773
                const KPDFPageRect * rect = pageItem->page()->getRect(
                    e->x() - pageItem->geometry().left(),
                    e->y() - pageItem->geometry().top()
                );
                if ( rect )
774
                {
775 776 777 778 779 780 781 782 783 784
                    // release over a link
                    if ( rect->pointerType() == KPDFPageRect::Link )
                    {
                        const KPDFLink * link = static_cast< const KPDFLink * >( rect->pointer() );
                        d->document->processLink( link );
                    }
                    // release over an image
                    if ( rect->pointerType() == KPDFPageRect::Image )
                    {
                    }
785 786 787 788
                }
                else
                {
                    // mouse not moved since press, so we have a click. select the page.
789
                    d->document->setViewportPage( pageItem->pageNumber(), PAGEVIEW_ID );
790
                }
791
            }
792
            else if ( rightButton )
793
            {
794 795
                if (pageItem) emit rightClick(pageItem->page(), e->globalPos());
                else emit rightClick(0, e->globalPos());
796
            }
797 798
            // reset start position
            d->mouseStartPos = QPoint();
799
            }break;
800

801
        case MouseZoom:     // do ZOOM
802 803
            if ( leftButton && !d->mouseSelectionRect.isNull() )
            {
804
                QRect selRect = d->mouseSelectionRect.normalize();
805
                if ( selRect.width() < 4 || selRect.height() < 4 )
806
                    break;
807

808 809
                // find out new zoom ratio and normalized view center (relative to the contentsRect)
                double zoom = QMIN( (double)visibleWidth() / (double)selRect.width(), (double)visibleHeight() / (double)selRect.height() );
810 811
                double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)contentsWidth());
                double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)contentsHeight());
812 813 814 815 816

                // zoom up to 400%
                if ( d->zoomFactor <= 4.0 || zoom <= 1.0 )
                {
                    d->zoomFactor *= zoom;
817
                    viewport()->setUpdatesEnabled( false );
818
                    updateZoom( ZoomRefreshCurrent );
819
                    viewport()->setUpdatesEnabled( true );
820 821
                }

822
                // recenter view and update the viewport
823
                center( (int)(nX * contentsWidth()), (int)(nY * contentsHeight()) );
824
                updateContents();
825 826 827 828 829 830

                // hide message box and delete overlay window
                selectionClear();
                return;
            }
            // if in ZoomRect mode, right click zooms out
831
            else if ( rightButton )
832 833 834 835 836 837
            {
                updateZoom( ZoomOut );
                return;
            }
            break;

838 839 840
        case MouseSelect:{  // do SELECT
            if ( !leftButton || d->mouseSelectionRect.isNull() )
                break;
841

842 843
            QRect selectionRect = d->mouseSelectionRect.normalize();
            if ( selectionRect.width() < 5 || selectionRect.height() < 5 )
844 845
            {
                selectionClear();
846
                break;
847
            }
Enrico Ros's avatar