pageview.cpp 54.3 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 29
#include <kiconloader.h>
#include <kurldrag.h>
#include <kaction.h>
#include <kactioncollection.h>
30
#include <kpopupmenu.h>
31
#include <klocale.h>
32
#include <kfiledialog.h>
33
#include <kimageeffect.h>
34
#include <kimageio.h>
35
#include <kdebug.h>
36

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

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

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

52 53 54 55 56 57
// structure used internally by PageView for data storage
class PageViewPrivate
{
public:
    // the document, current page and pages indices vector
    KPDFDocument * document;
58 59
    PageViewItem * activeItem; //equal to items[vectorIndex]
    QValueVector< PageViewItem * > items;
60
    int vectorIndex;
61
    QValueList< PageViewItem * > visibleItems;
62

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

    // other stuff
74 75
    QTimer * delayTimer;
    QTimer * scrollTimer;
76
    int scrollIncrement;
77
    bool dirtyLayout;
78
    PageViewMessage * messageWindow;    //in pageviewutils.h
79 80

    // actions
81 82 83 84 85 86 87
    KToggleAction * aMouseEdit;
    KSelectAction * aZoom;
    KToggleAction * aZoomFitWidth;
    KToggleAction * aZoomFitPage;
    KToggleAction * aZoomFitText;
    KToggleAction * aViewTwoPages;
    KToggleAction * aViewContinous;
88 89 90
};


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

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

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

134 135
    // show initial welcome text
    d->messageWindow->display( i18n( "Welcome" ), PageViewMessage::Info, 2000 );
136

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

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

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

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

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

170
    // View-Layout actions
171 172
    d->aViewTwoPages = new KToggleAction( i18n("Two Pages"), "view_left_right", 0, ac, "view_twopages" );
    connect( d->aViewTwoPages, SIGNAL( toggled( bool ) ), SLOT( slotTwoPagesToggled( bool ) ) );
173
    d->aViewTwoPages->setChecked( Settings::viewColumns() > 1 );
174

175 176
    d->aViewContinous = new KToggleAction( i18n("Continous"), "view_text", 0, ac, "view_continous" );
    connect( d->aViewContinous, SIGNAL( toggled( bool ) ), SLOT( slotContinousToggled( bool ) ) );
177
    d->aViewContinous->setChecked( Settings::viewContinous() );
178 179

    // Mouse-Mode actions
180
    KToggleAction * mn = new KRadioAction( i18n("Normal"), "mouse", 0, this, SLOT( slotSetMouseNormal() ), ac, "mouse_drag" );
181
    mn->setExclusiveGroup( "MouseType" );
182 183
    mn->setChecked( true );

184 185 186
    KToggleAction * mz = new KRadioAction( i18n("Zoom Tool"), "viewmag", 0, this, SLOT( slotSetMouseZoom() ), ac, "mouse_zoom" );
    mz->setExclusiveGroup( "MouseType" );

187
    KToggleAction * mst = new KRadioAction( i18n("Select"), "frame_edit", 0, this, SLOT( slotSetMouseSelect() ), ac, "mouse_select" );
188 189
    mst->setExclusiveGroup( "MouseType" );

190 191 192
    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
193 194

    // Other actions
195 196 197 198 199
    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" );
200 201
}

202 203 204 205 206 207 208 209 210 211 212 213
void PageView::setZoomFitWidth()
{
    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 );
    updateContents();
Albert Astals Cid's avatar
Albert Astals Cid committed
214
    updateZoomText();
215 216
}

217

218
//BEGIN KPDFDocumentObserver inherited methods
Enrico Ros's avatar
Enrico Ros committed
219 220 221
void PageView::pageSetup( const QValueVector<KPDFPage*> & pageSet, bool documentChanged )
{
    // reuse current pages if nothing new
222
    if ( ( pageSet.count() == d->items.count() ) && !documentChanged )
Enrico Ros's avatar
Enrico Ros committed
223 224 225
    {
        int count = pageSet.count();
        for ( int i = 0; (i < count) && !documentChanged; i++ )
226
            if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() )
Enrico Ros's avatar
Enrico Ros committed
227 228 229 230 231 232
                documentChanged = true;
        if ( !documentChanged )
            return;
    }

    // delete all widgets (one for each page in pageSet)
233
    QValueVector< PageViewItem * >::iterator dIt = d->items.begin(), dEnd = d->items.end();
234 235
    for ( ; dIt != dEnd; ++dIt )
        delete *dIt;
236
    d->items.clear();
237
    d->visibleItems.clear();
238
    d->activeItem = 0;
239 240 241 242

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

245
    // invalidate layout
246
    d->dirtyLayout = true;
247 248

    // OSD to display pages
249
    if ( documentChanged && pageSet.count() > 0 && Settings::showOSD() )
250
        d->messageWindow->display(
251 252
            i18n(" Loaded a one page document.",
                 " Loaded a %n pages document.",
253
                 pageSet.count() ),
254
            PageViewMessage::Info, 4000 );
255 256
}

257
void PageView::pageSetCurrent( int pageNumber, const QRect & viewport )
258
{
259 260
    // select next page
    d->vectorIndex = 0;
261
    d->activeItem = 0;
262 263
    QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end();
    for ( ; iIt != iEnd; ++iIt )
264
    {
265
        if ( (*iIt)->pageNumber() == pageNumber )
266
        {
267
            d->activeItem = *iIt;
268 269
            break;
        }
270
        d->vectorIndex ++;
271
    }
272
    if ( !d->activeItem )
273
        return;
274

Enrico Ros's avatar
Enrico Ros committed
275
    // relayout in "Single Pages" mode or if a relayout is pending
276
    if ( !Settings::viewContinous() || d->dirtyLayout )
277 278 279
        slotRelayoutPages();

    // center the view to see the selected page
280 281 282 283 284
    if ( viewport.isNull() || true ) // FIXME take care of viewport
    {
        const QRect & r = d->activeItem->geometry();
        center( r.left() + r.width() / 2, r.top() + visibleHeight() / 2 - 10 );
    }
285 286 287 288 289
    slotRequestVisiblePixmaps();

    // update zoom text if in a ZoomFit/* zoom mode
    if ( d->zoomMode != ZoomFixed )
        updateZoomText();
290 291 292 293 294

    // that is here because of that
    // you clicked on a link that brought you to another page
    // the page was on the cache and you have to update the cursor
    updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) );
295
}
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

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

void PageView::notifyPixmapChanged( int pageNumber )
{
    QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end();
    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 );
318

Albert Astals Cid's avatar
Albert Astals Cid committed
319
            // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor
320 321 322 323 324 325 326 327 328
            if (cursor().shape() != Qt::SizeVerCursor)
            {
                // that is here because of that
                // you clicked on a link that brought you to another page
                // the page was not on the cache so the updateCursor from pageSetCurrent does not work
                updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) );
            }


329 330 331 332 333 334 335 336
            break;
        }
}

void PageView::notifyPixmapsCleared()
{
    slotRequestVisiblePixmaps();
}
337 338
//END KPDFDocumentObserver inherited methods

339
//BEGIN widget events
340
void PageView::viewportPaintEvent( QPaintEvent * pe )
341
{
342
    // create the rect into contents from the clipped screen rect
343 344
    QRect viewportRect = viewport()->rect();
    QRect contentsRect = pe->rect().intersect( viewportRect );
345 346
    contentsRect.moveBy( contentsX(), contentsY() );
    if ( !contentsRect.isValid() )
347
        return;
348

349 350 351 352
    // 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() );
353

354 355 356 357 358 359 360 361 362 363 364
    // 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;

365
    // subdivide region into rects
366
    QMemArray<QRect> allRects = pe->region().rects();
367
    uint numRects = allRects.count();
368 369 370

    // preprocess rects area to see if it worths or not using subdivision
    uint summedArea = 0;
371
    for ( uint i = 0; i < numRects; i++ )
372
    {
373 374 375 376 377 378 379
        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;
380 381

    // iterate over the rects (only one loop if not using subdivision)
382 383 384 385 386 387 388 389 390 391
    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;
        }
392

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

        if ( wantCompositing && !Settings::disableCompositing() )
397
        {
398
            // create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0})
399 400 401 402
            QPixmap doubleBuffer( contentsRect.size() );
            QPainter pixmapPainter( &doubleBuffer );
            pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() );

403
            // 1) Layer 0: paint items and clear bg on unpainted rects
404
            paintItems( &pixmapPainter, contentsRect );
405 406
            // 2) Layer 1: pixmap manipulated areas
            // 3) Layer 2: paint (blend) transparent selection
407 408
            if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
                 !selectionRectInternal.contains( contentsRect ) )
409
            {
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
                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 );
428
            }
429
            // 4) Layer 3: overlays
430
            if ( Settings::debugDrawBoundaries() )
431 432 433 434
            {
                pixmapPainter.setPen( Qt::blue );
                pixmapPainter.drawRect( contentsRect );
            }
435

436 437 438 439
            // finish painting and draw contents
            pixmapPainter.end();
            screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer );
        }
440
        else
441
        {
442
            // 1) Layer 0: paint items and clear bg on unpainted rects
443
            paintItems( &screenPainter, contentsRect );
444 445
            // 2) Layer 1: opaque manipulated ares (filled / contours)
            // 3) Layer 2: paint opaque selection
446 447
            if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
                 !selectionRectInternal.contains( contentsRect ) )
448 449
            {
                screenPainter.setPen( palette().active().highlight().dark(110) );
450
                screenPainter.drawRect( selectionRect );
451
            }
452
            // 4) Layer 3: overlays
453
            if ( Settings::debugDrawBoundaries() )
454 455 456 457
            {
                screenPainter.setPen( Qt::red );
                screenPainter.drawRect( contentsRect );
            }
458
        }
459
    }
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
}

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() ) );
    }
    d->delayTimer->start( 400, true );
}

void PageView::keyPressEvent( QKeyEvent * e )
{
    e->accept();
    // move/scroll page by using keys
    switch ( e->key() )
    {
        case Key_Up:
480
        case Key_PageUp:
481
            // if in single page mode and at the top of the screen, go to previous page
482 483 484 485 486 487 488
            if ( Settings::viewContinous() || verticalScrollBar()->value() > verticalScrollBar()->minValue() )
            {
                if ( e->key() == Key_Up )
                    verticalScrollBar()->subtractLine();
                else
                    verticalScrollBar()->subtractPage();
            }
489
            else if ( d->vectorIndex > 0 )
490
                d->document->setCurrentPage( d->items[ d->vectorIndex - 1 ]->pageNumber() );
491 492
            break;
        case Key_Down:
493
        case Key_PageDown:
494
            // if in single page mode and at the bottom of the screen, go to next page
495 496 497 498 499 500 501
            if ( Settings::viewContinous() || verticalScrollBar()->value() < verticalScrollBar()->maxValue() )
            {
                if ( e->key() == Key_Down )
                    verticalScrollBar()->addLine();
                else
                    verticalScrollBar()->addPage();
            }
502
            else if ( d->vectorIndex < (int)d->items.count() - 1 )
503
                d->document->setCurrentPage( d->items[ d->vectorIndex + 1 ]->pageNumber() );
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
            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();
    }
}

void PageView::contentsMouseMoveEvent( QMouseEvent * e )
{
535 536 537 538 539 540 541
    // 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 );
542 543
        // uncomment following line to force a complete redraw
        viewport()->repaint( false );
544 545
        return;
    }
546

547
    bool leftButton = e->state() & LeftButton;
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
    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();
                    }
                }
            }
568
            else // only hovering the page
569
            {
570
                updateCursor( e->pos() );
Enrico Ros's avatar
Enrico Ros committed
571
            }
572 573
            break;

574
        case MouseZoom:
575
        case MouseSelect:
576
            // set second corner of selection in selection pageItem
577 578
            if ( leftButton && !d->mouseSelectionRect.isNull() )
                selectionEndPoint( e->x(), e->y() );
579 580 581 582 583 584 585
            break;

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

586 587
void PageView::contentsMousePressEvent( QMouseEvent * e )
{
588 589 590 591 592 593 594
    // 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;
    }
595 596

    // handle mode dependant mouse press actions
597
    bool leftButton = e->button() & LeftButton;
598 599
    switch ( d->mouseMode )
    {
600 601
        case MouseNormal:    // drag start / click / link following
            if ( leftButton )
602
            {
603
                d->mouseStartPos = e->globalPos();
604 605
                d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mouseStartPos;
                if ( !d->mouseOnRect )
606
                    setCursor( sizeAllCursor );
607
            }
608
            break;
609

610
        case MouseZoom:
611
        case MouseSelect:   // set first corner of the selection rect
612 613 614 615
            if ( leftButton )
                selectionStart( e->x(), e->y(), false );
            break;

616 617
        case MouseEdit:      // ? place the beginning of [tool] ?
            break;
618 619 620
    }
}

621
void PageView::contentsMouseReleaseEvent( QMouseEvent * e )
622
{
623 624 625 626
    // handle mode indepent mid buttom zoom
    if ( (e->state() & MidButton) && d->mouseMidStartY > 0 )
    {
        d->mouseMidStartY = -1;
627 628
        // while drag-zooming we could have gone over a link
        updateCursor( e->pos() );
629 630 631
        return;
    }

632 633
    bool leftButton = e->button() & LeftButton,
         rightButton = e->button() & RightButton;
634 635
    switch ( d->mouseMode )
    {
636
        case MouseNormal:{  // do Follow Link or Display RMB
637 638 639
            // 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
640 641
            PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );

642 643 644
            // 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
645
            if ( leftButton && pageItem && d->mouseStartPos == e->globalPos())
646
            {
647 648 649 650 651
                const KPDFPageRect * rect = pageItem->page()->getRect(
                    e->x() - pageItem->geometry().left(),
                    e->y() - pageItem->geometry().top()
                );
                if ( rect )
652
                {
653 654 655 656 657 658 659 660 661 662
                    // 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 )
                    {
                    }
663 664 665 666
                }
                else
                {
                    // mouse not moved since press, so we have a click. select the page.
667
                    d->document->setCurrentPage( pageItem->pageNumber() );
668
                }
669
            }
670
            else if ( rightButton )
671
            {
672 673
                if (pageItem) emit rightClick(pageItem->page(), e->globalPos());
                else emit rightClick(0, e->globalPos());
674
            }
675 676
            // reset start position
            d->mouseStartPos = QPoint();
677
            }break;
678

679
        case MouseZoom:     // do ZOOM
680 681
            if ( leftButton && !d->mouseSelectionRect.isNull() )
            {
682
                QRect selRect = d->mouseSelectionRect.normalize();
683
                if ( selRect.width() < 4 || selRect.height() < 4 )
684
                    break;
685

686 687
                // 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() );
688 689
                double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)contentsWidth());
                double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)contentsHeight());
690 691 692 693 694

                // zoom up to 400%
                if ( d->zoomFactor <= 4.0 || zoom <= 1.0 )
                {
                    d->zoomFactor *= zoom;
695
                    viewport()->setUpdatesEnabled( false );
696
                    updateZoom( ZoomRefreshCurrent );
697
                    viewport()->setUpdatesEnabled( true );
698 699
                }

700
                // recenter view and update the viewport
701
                center( (int)(nX * contentsWidth()), (int)(nY * contentsHeight()) );
702
                updateContents();
703 704 705 706 707 708

                // hide message box and delete overlay window
                selectionClear();
                return;
            }
            // if in ZoomRect mode, right click zooms out
709
            else if ( rightButton )
710 711 712 713 714 715
            {
                updateZoom( ZoomOut );
                return;
            }
            break;

716 717 718
        case MouseSelect:{  // do SELECT
            if ( !leftButton || d->mouseSelectionRect.isNull() )
                break;
719

720 721
            QRect selectionRect = d->mouseSelectionRect.normalize();
            if ( selectionRect.width() < 5 || selectionRect.height() < 5 )
722 723
            {
                selectionClear();
724
                break;
725
            }
726

727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744
            // grab text in selection by extracting it from all intersected pages
            QString selectedText;
            QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end();
            for ( ; iIt != iEnd; ++iIt )
            {
                PageViewItem * item = *iIt;
                const QRect & itemRect = item->geometry();
                if ( selectionRect.intersects( itemRect ) )
                {
                    // request the textpage if there isn't one
                    const KPDFPage * kpdfPage = item->page();
                    if ( !kpdfPage->hasSearchPage() )
                        d->document->requestTextPage( kpdfPage->number() );
                    // grab text
                    QRect relativeRect = selectionRect.intersect( itemRect );
                    relativeRect.moveBy( -itemRect.left(), -itemRect.top() );
                    selectedText += kpdfPage->getTextInRect( relativeRect, item->zoomFactor() );
                }
745 746
            }

747 748 749 750 751 752 753 754 755
            // popup that ask to copy:text and copy/save:image
            KPopupMenu menu( this );
            if ( !selectedText.isEmpty() )
            {
                menu.insertTitle( i18n( "Text ( %1 characters )" ).arg( selectedText.length() ) );
                menu.insertItem( SmallIcon("editcopy"), i18n( "Copy to Clipboard" ), 1 );
            }
            menu.insertTitle( i18n( "Image ( %1 by %2 pixels )" ).arg( selectionRect.width() ).arg( selectionRect.height() ) );
            menu.insertItem( SmallIcon("image"), i18n( "Copy to Clipboard" ), 2 );
756
            menu.insertItem( SmallIcon("filesave"), i18n( "Save to File..." ), 3 );
757 758 759
            int choice = menu.exec( e->globalPos() );
            // IMAGE operation choosen
            if ( choice > 1 )
760
            {
761 762 763 764 765 766 767
                // renders page into a pixmap
                QPixmap copyPix( selectionRect.width(), selectionRect.height() );
                QPainter copyPainter( &copyPix );
                copyPainter.translate( -selectionRect.left(), -selectionRect.top() );
                paintItems( &copyPainter, selectionRect );

                if ( choice == 2 )
768
                {
769 770 771 772 773 774 775 776 777 778 779 780 781 782
                    // [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." ).arg( copyPix.width() ).arg( copyPix.height() ) );
                }
                else
                {
                    // [3] save pixmap to file
                    QString fileName = KFileDialog::getSaveFileName( QString::null, "image/png image/jpeg", this );
                    if ( fileName.isNull() )
                        d->messageWindow->display( i18n( "File not saved." ), PageViewMessage::Warning );
                    else
783
                    {
784 785 786 787 788
                        QString type( KImageIO::type( fileName ) );
                        if ( type.isNull() )
                            type = "PNG";
                        copyPix.save( fileName, type.latin1() );
                        d->messageWindow->display( i18n( "Image [%1x%2] saved to %3 file." ).arg( copyPix.width() ).arg( copyPix.height() ).arg( type ) );
789 790
                    }
                }
791
            }
792 793 794 795 796 797 798 799 800 801 802 803
            // TEXT operation choosen
            else if ( choice == 1 )
            {
                QClipboard *cb = QApplication::clipboard();
                cb->setText( selectedText, QClipboard::Clipboard );
                if ( cb->supportsSelection() )
                    cb->setText( selectedText, QClipboard::Selection );
            }

            // clear widget selection and invalidate rect
            selectionClear();
            }break;
804

805 806
        case MouseEdit:      // ? apply [tool] ?
            break;
807 808 809 810 811
    }
}

void PageView::wheelEvent( QWheelEvent *e )
{
812 813
    int delta = e->delta(),
        vScroll = verticalScrollBar()->value();
814 815
    e->accept();
    if ( (e->state() & ControlButton) == ControlButton ) {
816
        if ( e->delta() < 0 )
817 818 819 820
            slotZoomOut();
        else
            slotZoomIn();
    }
821
    else if ( delta <= -120 && !Settings::viewContinous() && vScroll == verticalScrollBar()->maxValue() )
822
    {
823
        // go to next page
824
        if ( d->vectorIndex < (int)d->items.count() - 1 )
825
            d->document->setCurrentPage( d->items[ d->vectorIndex + 1 ]->pageNumber() );
826
    }
827 828 829 830
    else if ( delta >= 120 && !Settings::viewContinous() && vScroll == verticalScrollBar()->minValue() )
    {
        // go to prev page
        if ( d->vectorIndex > 0 )
831
        {
832
            d->document->setCurrentPage( d->items[ d->vectorIndex - 1 ]->pageNumber() );
833 834
            verticalScrollBar()->setValue(verticalScrollBar()->maxValue());
        }
835 836 837
    }
    else
        QScrollView::wheelEvent( e );
838

839 840
    QPoint cp = viewportToContents(e->pos());
    updateCursor(cp);
841 842
}

843 844 845 846 847 848 849 850 851 852 853 854 855
void PageView::dragEnterEvent( QDragEnterEvent * ev )
{
    ev->accept();
}

void PageView::dropEvent( QDropEvent * ev )
{
    KURL::List lst;
    if (  KURLDrag::decode(  ev, lst ) )
        emit urlDropped( lst.first() );
}
//END widget events

856
void PageView::paintItems( QPainter * p, const QRect & contentsRect )
857 858 859 860 861 862 863
{
    // 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.addCoords( -3, -3, 1, 1 );

864 865 866
    // create a region from wich we'll subtract painted rects
    QRegion remainingArea( contentsRect );

867 868
    QValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end();
    for ( ; iIt != iEnd; ++iIt )
869 870
    {
        // check if a piece of the page intersects the contents rect
871 872
        if ( !(*iIt)->geometry().intersects( checkRect ) )
            continue;
873

874 875
        PageViewItem * item = *iIt;
        QRect pixmapGeometry = item->geometry();