pageview.cpp 92.8 KB
Newer Older
1
/***************************************************************************
2
 *   Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it>             *
Albert Astals Cid's avatar
Albert Astals Cid committed
3
 *   Copyright (C) 2004-2006 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 20 21 22
#include <X11/Xlib.h>
#include <X11/extensions/Xrender.h>
#include <fixx11h.h>

23
// qt/kde includes
24
#include <qcursor.h>
25
#include <qevent.h>
26 27
#include <qpainter.h>
#include <qtimer.h>
Enrico Ros's avatar
Enrico Ros committed
28
#include <qdatetime.h>
29
#include <qpushbutton.h>
Enrico Ros's avatar
Enrico Ros committed
30 31
#include <qapplication.h>
#include <qclipboard.h>
32
#include <dcopclient.h>
33 34
#include <kiconloader.h>
#include <kaction.h>
Jakub Stachowski's avatar
Jakub Stachowski committed
35
#include <kstdaccel.h>
36
#include <kactioncollection.h>
37
#include <kmenu.h>
38
#include <klocale.h>
39
#include <kfiledialog.h>
40
#include <kimageeffect.h>
41
#include <kimageio.h>
42
#include <ktoolinvocation.h>
43
#include <kdebug.h>
44
#include <kmessagebox.h>
45

46
// system includes
47
#include <math.h>
48
#include <stdlib.h>
49

50
// local includes
51
#include "pageview.h"
52
#include "pageviewutils.h"
53
#include "pagepainter.h"
54
#include "pageviewannotator.h"
55
#include "core/document.h"
56
#include "core/page.h"
57
#include "core/misc.h"
58 59 60
#include "core/link.h"
#include "core/generator.h"
#include "conf/settings.h"
61

62 63
#define ROUND(x) (int(x + 0.5))

64 65 66
// definition of searchID for this class
#define PAGEVIEW_SEARCH_ID 2

67 68 69 70
// structure used internally by PageView for data storage
class PageViewPrivate
{
public:
71
    // the document, pageviewItems and the 'visible cache'
72
    KPDFDocument * document;
73 74
    QVector< PageViewItem * > items;
    QLinkedList< PageViewItem * > visibleItems;
75

76
    // view layout (columns and continuous in Settings), zoom and mouse
77 78 79 80
    PageView::ZoomMode zoomMode;
    float zoomFactor;
    PageView::MouseMode mouseMode;
    QPoint mouseGrabPos;
81
    QPoint mousePressPos;
82
    QPoint mouseSelectPos;
83 84
    bool mouseMidZooming;
    int mouseMidLastY;
85
    bool mouseSelecting;
86
    QRect mouseSelectionRect;
87
    QColor mouseSelectionColor;
88 89
    bool mouseTextSelecting;
    bool mouseTextSelectionPainted;
90
    QList<QRect>* mouseTextSelectionRect;
91 92
    QColor mouseTextSelectionColor;
    TextSelection * mouseTextSelectionInfo;
93
    bool mouseOnRect;
94
                             
Enrico Ros's avatar
Enrico Ros committed
95 96 97 98 99 100 101 102 103 104
    // type ahead find
    bool typeAheadActive;
    QString typeAheadString;
    QTimer * findTimeoutTimer;
    // viewport move
    bool viewportMoveActive;
    QTime viewportMoveTime;
    QPoint viewportMoveDest;
    QTimer * viewportMoveTimer;
    // auto scroll
105
    int scrollIncrement;
Enrico Ros's avatar
Enrico Ros committed
106
    QTimer * autoScrollTimer;
107
    // annotations
108
    PageViewAnnotator * annotator;
Enrico Ros's avatar
Enrico Ros committed
109 110
    // other stuff
    QTimer * delayResizeTimer;
111
    bool dirtyLayout;
Enrico Ros's avatar
Enrico Ros committed
112
    bool blockViewport;                 // prevents changes to viewport
113 114
    bool blockPixmapsRequest;           // prevent pixmap requests
    PageViewMessage * messageWindow;    // in pageviewutils.h
115 116

    // actions
117
    KSelectAction * aOrientation;
118
    KSelectAction * aPaperSizes;
Pino Toscano's avatar
porting  
Pino Toscano committed
119 120
    KAction * aMouseNormal;
    KAction * aMouseSelect;
121
    KToggleAction * aToggleAnnotator;
122 123 124 125
    KSelectAction * aZoom;
    KToggleAction * aZoomFitWidth;
    KToggleAction * aZoomFitPage;
    KToggleAction * aZoomFitText;
126
    KSelectAction * aRenderMode;
127
    KToggleAction * aViewContinuous;
128
    KAction * aPrevAction;
129 130 131
};


132

133 134 135 136
/* 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)
137
 *  550 - events: mouse, keyboard, drag/drop
138
 *  170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes
139
 *  100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc..
140
 *  other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable,
141
 * and many insignificant stuff like this comment :-)
142 143
 */
PageView::PageView( QWidget *parent, KPDFDocument *document )
144
    : Q3ScrollView( parent, "KPDF::pageView", Qt::WStaticContents | Qt::WNoAutoErase )
145 146 147 148
{
    // create and initialize private storage structure
    d = new PageViewPrivate();
    d->document = document;
149
    d->aOrientation = 0;
150
    d->aRenderMode = 0;
Piotr Szymanski's avatar
Piotr Szymanski committed
151 152
    d->zoomMode = (PageView::ZoomMode) KpdfSettings::zoomMode();
    d->zoomFactor = KpdfSettings::zoomFactor();
153
    d->mouseMode = MouseNormal;
154
    d->mouseMidZooming = false;
155
    d->mouseSelecting = false;
156
    d->mouseTextSelecting = false;
157
    d->mouseOnRect = false;
Enrico Ros's avatar
Enrico Ros committed
158 159 160 161
    d->typeAheadActive = false;
    d->findTimeoutTimer = 0;
    d->viewportMoveActive = false;
    d->viewportMoveTimer = 0;
162
    d->scrollIncrement = 0;
Enrico Ros's avatar
Enrico Ros committed
163
    d->autoScrollTimer = 0;
164
    d->annotator = 0;
Enrico Ros's avatar
Enrico Ros committed
165
    d->delayResizeTimer = 0;
166
    d->dirtyLayout = false;
167
    d->blockViewport = false;
168
    d->blockPixmapsRequest = false;
169
    d->messageWindow = new PageViewMessage(this);
170
    d->aPrevAction = 0;
171 172 173 174
    d->mouseTextSelectionRect=0;
    d->mouseTextSelectionInfo=0;
    d->mouseTextSelectionPainted=0;
    d->aPaperSizes=0;
175 176 177

    // widget setup: setup focus, accept drops and track mouse
    viewport()->setFocusProxy( this );
178
    viewport()->setFocusPolicy( Qt::StrongFocus );
179 180
    //viewport()->setPaletteBackgroundColor( Qt::white );
    viewport()->setBackgroundMode( Qt::NoBackground );
181
    setResizePolicy( Manual );
182
    setAcceptDrops( true );
183
    setDragAutoScroll( false );
184
    viewport()->setMouseTracking( true );
185 186 187

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

189 190
    // show initial welcome text
    d->messageWindow->display( i18n( "Welcome" ), PageViewMessage::Info, 2000 );
191

192
    // set a corner button to resize the view to the page size
193 194 195 196
//    QPushButton * resizeButton = new QPushButton( viewport() );
//    resizeButton->setPixmap( SmallIcon("crop") );
//    setCornerWidget( resizeButton );
//    resizeButton->setEnabled( false );
197 198 199 200 201
    // connect(...);
}

PageView::~PageView()
{
202
    // delete the local storage structure
Albert Astals Cid's avatar
Albert Astals Cid committed
203
    d->document->removeObserver( this );
204 205 206
    delete d;
}

207
void PageView::setupActions( KActionCollection * ac )
208
{
209 210
    d->aOrientation=new KSelectAction( i18n( "&Orientation" ), 0, this, 0, ac, "view_orientation" );
    d->aPaperSizes=new KSelectAction( i18n( "&Paper sizes" ), 0, this, 0, ac, "view_papersizes" );
211 212 213 214 215 216 217 218 219
    QStringList orientations;
    orientations.append( i18n( "Portrait" ) );
    orientations.append( i18n( "Landscape" ) );
    orientations.append( i18n( "Upside Down" ) );
    orientations.append( i18n( "Seascape" ) );
    d->aOrientation->setItems( orientations );

    connect( d->aOrientation , SIGNAL( activated( int ) ),
         d->document , SLOT( slotOrientation( int ) ) );
220 221
    connect( d->aPaperSizes , SIGNAL( activated( int ) ),
         d->document , SLOT( slotPaperSizes( int ) ) );
222 223

    d->aOrientation->setEnabled(d->document->supportsRotation());
224 225 226 227
    bool paperSizes=d->document->supportsPaperSizes();
    d->aPaperSizes->setEnabled(paperSizes);
    if (paperSizes)
      d->aPaperSizes->setItems(d->document->paperSizes());
228

229
    // Zoom actions ( higher scales takes lots of memory! )
230
    d->aZoom = new KSelectAction( i18n( "Zoom" ), "viewmag", 0, this, SLOT( slotZoom() ), ac, "zoom_to" );
231
    d->aZoom->setEditable( true );
Enrico Ros's avatar
Enrico Ros committed
232 233 234
#if KDE_IS_VERSION(3,4,89)
    d->aZoom->setMaxComboViewCount( 13 );
#endif
235
    updateZoomText();
236 237 238 239 240

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

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

Enrico Ros's avatar
Enrico Ros committed
241
    d->aZoomFitWidth = new KToggleAction( i18n("Fit to Page &Width"), "view_fit_width", 0, ac, "zoom_fit_width" );
242 243
    connect( d->aZoomFitWidth, SIGNAL( toggled( bool ) ), SLOT( slotFitToWidthToggled( bool ) ) );

Enrico Ros's avatar
Enrico Ros committed
244
    d->aZoomFitPage = new KToggleAction( i18n("Fit to &Page"), "view_fit_window", 0, ac, "zoom_fit_page" );
245 246
    connect( d->aZoomFitPage, SIGNAL( toggled( bool ) ), SLOT( slotFitToPageToggled( bool ) ) );

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

250
    // View-Layout actions
251 252 253 254 255 256 257 258 259
    QStringList renderModes;
    renderModes.append( i18n( "Single" ) );
    renderModes.append( i18n( "Facing" ) );
    renderModes.append( i18n( "Overview" ) );

    d->aRenderMode = new KSelectAction( i18n("&Render Mode"), "view_left_right", 0, ac, "view_render_mode" );
    connect( d->aRenderMode, SIGNAL( activated( int ) ), SLOT( slotRenderMode( int ) ) );
    d->aRenderMode->setItems( renderModes );
    d->aRenderMode->setCurrentItem( KpdfSettings::renderMode() );
260

261 262
    d->aViewContinuous = new KToggleAction( i18n("&Continuous"), "view_text", 0, ac, "view_continuous" );
    connect( d->aViewContinuous, SIGNAL( toggled( bool ) ), SLOT( slotContinuousToggled( bool ) ) );
Piotr Szymanski's avatar
Piotr Szymanski committed
263
    d->aViewContinuous->setChecked( KpdfSettings::viewContinuous() );
264 265

    // Mouse-Mode actions
Pino Toscano's avatar
porting  
Pino Toscano committed
266 267 268 269
    QActionGroup * actGroup = new QActionGroup( this );
    actGroup->setExclusive( true );
    d->aMouseNormal = new KAction( i18n("&Browse Tool"), "mouse", 0, this, SLOT( slotSetMouseNormal() ), ac, "mouse_drag" );
    d->aMouseNormal->setActionGroup( actGroup );
270
    d->aMouseNormal->setChecked( true );
271

Pino Toscano's avatar
porting  
Pino Toscano committed
272 273
    KAction * mz = new KAction( i18n("&Zoom Tool"), "viewmag", 0, this, SLOT( slotSetMouseZoom() ), ac, "mouse_zoom" );
    mz->setActionGroup( actGroup );
274

Pino Toscano's avatar
porting  
Pino Toscano committed
275 276
    d->aMouseSelect = new KAction( i18n("&Select Tool"), "frame_edit", 0, this, SLOT( slotSetMouseSelect() ), ac, "mouse_select" );
    d->aMouseSelect->setActionGroup( actGroup );
277

278 279 280
    d->aToggleAnnotator = new KToggleAction( i18n("&Review"), "pencil", 0, ac, "mouse_toggle_annotate" );
    connect( d->aToggleAnnotator, SIGNAL( toggled( bool ) ), SLOT( slotToggleAnnotator( bool ) ) );
    d->aToggleAnnotator->setShortcut( "F6" );
281 282

    // Other actions
283 284 285 286 287
    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" );
288 289
}

290 291
bool PageView::canFitPageWidth()
{
292
    return KpdfSettings::renderMode() != 0 || d->zoomMode != ZoomFitWidth;
293 294
}

295
void PageView::fitPageWidth( int page )
296
{
297
    // zoom: Fit Width, columns: 1. setActions + relayout + setPage + update
298
    d->zoomMode = ZoomFitWidth;
299
    KpdfSettings::setRenderMode( 0 );
300 301 302
    d->aZoomFitWidth->setChecked( true );
    d->aZoomFitPage->setChecked( false );
    d->aZoomFitText->setChecked( false );
303
    d->aRenderMode->setCurrentItem( 0 );
304 305 306
    viewport()->setUpdatesEnabled( false );
    slotRelayoutPages();
    viewport()->setUpdatesEnabled( true );
307
    d->document->setViewportPage( page );
308
    updateZoomText();
Enrico Ros's avatar
Enrico Ros committed
309
    setFocus();
310 311
}

312
void PageView::displayMessage( const QString & message,PageViewMessage::Icon icon,int duration )
313
{
Piotr Szymanski's avatar
Piotr Szymanski committed
314
    if ( !KpdfSettings::showOSD() )
315 316 317 318 319 320
    {
        if (icon == PageViewMessage::Error)
            KMessageBox::error( this, message );
        else
            return;
    }
321 322 323 324 325 326

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

    // display message (duration is length dependant)
327 328 329
    if (duration==-1)
        duration = 500 + 100 * message.length();
    d->messageWindow->display( message, icon, duration );
330 331
}

332
//BEGIN DocumentObserver inherited methods
333
void PageView::notifySetup( const QVector< KPDFPage * > & pageSet, bool documentChanged )
Enrico Ros's avatar
Enrico Ros committed
334 335
{
    // reuse current pages if nothing new
336
    if ( ( pageSet.count() == d->items.count() ) && !documentChanged )
Enrico Ros's avatar
Enrico Ros committed
337 338 339
    {
        int count = pageSet.count();
        for ( int i = 0; (i < count) && !documentChanged; i++ )
340
            if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() )
Enrico Ros's avatar
Enrico Ros committed
341 342 343 344
                documentChanged = true;
        if ( !documentChanged )
            return;
    }
345
 
Enrico Ros's avatar
Enrico Ros committed
346
    // delete all widgets (one for each page in pageSet)
347
    QVector< PageViewItem * >::iterator dIt = d->items.begin(), dEnd = d->items.end();
348 349
    for ( ; dIt != dEnd; ++dIt )
        delete *dIt;
350
    d->items.clear();
351
    d->visibleItems.clear();
352 353

    // create children widgets
354
    QVector< KPDFPage * >::const_iterator setIt = pageSet.begin(), setEnd = pageSet.end();
355
    for ( ; setIt != setEnd; ++setIt )
356
    {
357
        d->items.push_back( new PageViewItem( *setIt ) );
358 359 360 361
#ifdef PAGEVIEW_DEBUG
        kdDebug() << "geom for " << d->items.last()->pageNumber() << " is " << d->items.last()->geometry() << endl;
#endif
    }
362

363
    // invalidate layout so relayout/repaint will happen on next viewport change
364
    if ( pageSet.count() > 0 )
365 366 367 368 369
        // TODO for Enrico: Check if doing always the slotRelayoutPages() is not
        // suboptimal in some cases, i'd say it is not but a recheck will not hurt
        // Need slotRelayoutPages() here instead of d->dirtyLayout = true
        // because opening a pdf from another pdf will not trigger a viewportchange
        // so pages are never relayouted
370
        QTimer::singleShot(0, this, SLOT(slotRelayoutPages())); // was used
371
    else
Piotr Szymanski's avatar
Piotr Szymanski committed
372 373 374 375
    {
        // update the mouse cursor when closing because we may have close through a link and
        // want the cursor to come back to the normal cursor
        updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) );
376
        resizeContents( 0, 0 );
Piotr Szymanski's avatar
Piotr Szymanski committed
377
    }
378 379

    // OSD to display pages
Piotr Szymanski's avatar
Piotr Szymanski committed
380
    if ( documentChanged && pageSet.count() > 0 && KpdfSettings::showOSD() )
381
        d->messageWindow->display(
Andrew Coles's avatar
 
Andrew Coles committed
382 383
            i18n(" Loaded a one-page document.",
                 " Loaded a %n-page document.",
384
                 pageSet.count() ),
385
            PageViewMessage::Info, 4000 );
386 387

    d->aOrientation->setEnabled(d->document->supportsRotation());
388 389 390 391
    bool paperSizes=d->document->supportsPaperSizes();
    d->aPaperSizes->setEnabled(paperSizes);
    if (paperSizes)
      d->aPaperSizes->setItems(d->document->paperSizes());
392 393
}

Enrico Ros's avatar
Enrico Ros committed
394
void PageView::notifyViewportChanged( bool smoothMove )
395
{
396 397 398 399 400 401 402 403
    // 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
404
    const DocumentViewport & vp = d->document->viewport();
405
    PageViewItem * item = 0;
406
    QVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end();
407
    for ( ; iIt != iEnd; ++iIt )
408
        if ( (*iIt)->pageNumber() == vp.pageNumber )
409
        {
410
            item = *iIt;
411 412
            break;
        }
413 414 415
    if ( !item )
    {
        kdDebug() << "viewport has no matching item!" << endl;
Piotr Szymanski's avatar
Piotr Szymanski committed
416
        d->blockViewport = false;
417
        return;
418
    }
419 420 421
#ifdef PAGEVIEW_DEBUG
    kdDebug() << "document viewport changed\n";
#endif
Enrico Ros's avatar
Enrico Ros committed
422
    // relayout in "Single Pages" mode or if a relayout is pending
423
    d->blockPixmapsRequest = true;
Piotr Szymanski's avatar
Piotr Szymanski committed
424
    if ( !KpdfSettings::viewContinuous() || d->dirtyLayout )
425 426
        slotRelayoutPages();

Enrico Ros's avatar
Enrico Ros committed
427
    // restore viewport center or use default {x-center,v-top} alignment
428
    const QRect & r = item->geometry();
Enrico Ros's avatar
Enrico Ros committed
429 430
    int newCenterX = r.left(),
        newCenterY = r.top();
Enrico Ros's avatar
Enrico Ros committed
431
    if ( vp.rePos.enabled )
432
    {
Enrico Ros's avatar
Enrico Ros committed
433 434 435 436 437 438 439 440 441 442 443
        if ( vp.rePos.pos == DocumentViewport::Center )
        {
            newCenterX += (int)( vp.rePos.normalizedX * (double)r.width() );
            newCenterY += (int)( vp.rePos.normalizedY * (double)r.height() );
        }
        else
        {
            // TopLeft
            newCenterX += (int)( vp.rePos.normalizedX * (double)r.width() + viewport()->width() / 2 );
            newCenterY += (int)( vp.rePos.normalizedY * (double)r.height() + viewport()->height() / 2 );
        }
Enrico Ros's avatar
Enrico Ros committed
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
    }
    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 );
467
    }
468
    else
Enrico Ros's avatar
Enrico Ros committed
469
        center( newCenterX, newCenterY );
470
    d->blockPixmapsRequest = false;
471 472

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

475 476 477
    // enable setViewport calls
    d->blockViewport = false;

478 479 480
    // update zoom text if in a ZoomFit/* zoom mode
    if ( d->zoomMode != ZoomFixed )
        updateZoomText();
481

482
    // since the page has moved below cursor, update it
483
    updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) );
484
}
485

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

492
    // iterate over visible items: if page(pageNumber) is one of them, repaint it
493
    QLinkedList< PageViewItem * >::iterator iIt = d->visibleItems.begin(), iEnd = d->visibleItems.end();
494 495 496 497 498 499 500
    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 );
501

Albert Astals Cid's avatar
Albert Astals Cid committed
502
            // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor
503
            if ( cursor().shape() != Qt::SizeVerCursor )
504
            {
505
                // since the page has been regenerated below cursor, update it
506 507
                updateCursor( viewportToContents( mapFromGlobal( QCursor::pos() ) ) );
            }
508 509 510 511
            break;
        }
}

512
void PageView::notifyContentsCleared( int changedFlags )
513
{
514 515 516
    // if pixmaps were cleared, re-ask them
    if ( changedFlags & DocumentObserver::Pixmap )
        slotRequestVisiblePixmaps();
517
}
518 519 520 521

bool PageView::canUnloadPixmap( int pageNumber )
{
    // if the item is visible, forbid unloading
522
    QLinkedList< PageViewItem * >::iterator vIt = d->visibleItems.begin(), vEnd = d->visibleItems.end();
523 524 525 526 527 528 529
    for ( ; vIt != vEnd; ++vIt )
        if ( (*vIt)->pageNumber() == pageNumber )
            return false;
    // if hidden premit unloading
    return true;
}
//END DocumentObserver inherited methods
530

531
//BEGIN widget events
532
void PageView::viewportPaintEvent( QPaintEvent * pe )
533 534 535
{

if ( d->document->handleEvent( pe ) )
536
{
537
    // create the rect into contents from the clipped screen rect
538 539
    QRect viewportRect = viewport()->rect();
    QRect contentsRect = pe->rect().intersect( viewportRect );
540 541
    contentsRect.moveBy( contentsX(), contentsY() );
    if ( !contentsRect.isValid() )
542
        return;
543

544
    // create the screen painter. a pixel painted at contentsX,contentsY
545
    // appears to the top-left corner of the scrollview.
546
    QPainter screenPainter( viewport() );
547
    screenPainter.translate( -contentsX(), -contentsY() );
548

549 550 551 552 553 554 555 556
    // 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
557
    QColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ?
558
                           d->mouseSelectionColor : Qt::red;
559

560
    // subdivide region into rects
561 562
    QVector<QRect> allRects = pe->region().rects();
    int numRects = allRects.count();
563 564 565

    // preprocess rects area to see if it worths or not using subdivision
    uint summedArea = 0;
566
    for ( int i = 0; i < numRects; i++ )
567
    {
568 569 570 571
        const QRect & r = allRects[i];
        summedArea += r.width() * r.height();
    }
    // very elementary check: SUMj(Region[j].area) is less than boundingRect.area
Enrico Ros's avatar
Enrico Ros committed
572
    bool useSubdivision = summedArea < (0.6 * contentsRect.width() * contentsRect.height());
573 574
    if ( !useSubdivision )
        numRects = 1;
575 576

    // iterate over the rects (only one loop if not using subdivision)
577 578 579 580 581 582 583 584 585 586
    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;
        }
587

588
        // note: this check will take care of all things requiring alpha blending (not only selection)
589 590
        bool wantCompositing = ( !selectionRect.isNull() && contentsRect.intersects( selectionRect ) )
            || d->mouseTextSelecting;
591

Piotr Szymanski's avatar
Piotr Szymanski committed
592
        if ( wantCompositing && KpdfSettings::enableCompositing() )
593
        {
594
            // create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0})
595 596 597 598
            QPixmap doubleBuffer( contentsRect.size() );
            QPainter pixmapPainter( &doubleBuffer );
            pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() );

599 600 601 602 603 604 605 606 607
            // calculate the color
            XRenderColor col;
            float alpha=0.2f;
            QColor blCol=selBlendColor.dark(140);
            col.red=( (blCol.red() << 8) | blCol.red() ) * alpha;
            col.green=( (blCol.green() << 8) | blCol.green() )*alpha;
            col.blue=( (blCol.blue() << 8) | blCol.blue())*alpha;
            col.alpha=alpha*0xffff;

608
            // 1) Layer 0: paint items and clear bg on unpainted rects
Enrico Ros's avatar
Enrico Ros committed
609
            drawDocumentOnPainter( contentsRect, &pixmapPainter );
610
            // 2) Layer 1a: paint (blend) transparent selection
611 612
            if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
                 !selectionRectInternal.contains( contentsRect ) )
613
            {
614 615 616 617 618 619 620 621 622 623
                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
624 625
//                     QImage blendedImage = blendedPixmap.convertToImage();
//                     KImageEffect::blend( selBlendColor.dark(140), blendedImage, 0.2 );
626
                    XRenderFillRectangle(x11Display(), PictOpOver, blendedPixmap.handle(), &col, 
627
                      0,0, blendRect.width(), blendRect.height());
628
                    // copy the blended pixmap back to its place
629
                    pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap );
630 631 632 633
                }
                // draw border (red if the selection is too small)
                pixmapPainter.setPen( selBlendColor );
                pixmapPainter.drawRect( selectionRect );
634 635 636 637
            }
            if ( d->mouseTextSelecting )
            {
              QRect blendRect;
Pino Toscano's avatar
porting  
Pino Toscano committed
638
              QList<QRect>::iterator it=d->mouseTextSelectionRect->begin(),
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
              end=d->mouseTextSelectionRect->end();
              XRenderColor col;
              float alpha=0.2f;
              QColor blCol=d->mouseTextSelectionColor.dark(140);
              col.red=( (blCol.red() << 8) | blCol.red() ) * alpha;
              col.green=( (blCol.green() << 8) | blCol.green() )*alpha;
              col.blue=( (blCol.blue() << 8) | blCol.blue())*alpha;
              col.alpha=alpha*0xffff;

              for (;it!=end;++it)
              {
                if (! ((*it).intersects( contentsRect )))
                  continue;
                blendRect = (*it).intersect(contentsRect);
                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();
659
                XRenderFillRectangle(x11Display(), PictOpOver, blendedPixmap.handle(), &col, 
660 661 662 663 664 665 666 667 668 669 670 671 672
                  0,0, blendRect.width(), blendRect.height());

//                 KImageEffect::blend( d->mouseTextSelectionColor.dark(140), blendedImage, 0.2 );

                // copy the blended pixmap back to its place
                pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap );
              
                // draw border (red if the selection is too small)
                pixmapPainter.setPen( d->mouseTextSelectionColor );
                pixmapPainter.drawRect( selectionRect );
              }
                 
              
673
            }
Enrico Ros's avatar
Enrico Ros committed
674 675 676 677
            // 3) Layer 1: give annotator painting control
            if ( d->annotator && d->annotator->routePaints( contentsRect ) )
                d->annotator->routePaint( &pixmapPainter, contentsRect );
            // 4) Layer 2: overlays
Piotr Szymanski's avatar
Piotr Szymanski committed
678
            if ( KpdfSettings::debugDrawBoundaries() )
679 680 681 682
            {
                pixmapPainter.setPen( Qt::blue );
                pixmapPainter.drawRect( contentsRect );
            }
683

684 685 686 687
            // finish painting and draw contents
            pixmapPainter.end();
            screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer );
        }
688
        else
689
        {
690
            // 1) Layer 0: paint items and clear bg on unpainted rects
Enrico Ros's avatar
Enrico Ros committed
691 692
            drawDocumentOnPainter( contentsRect, &screenPainter );
            // 2) Layer 1: paint opaque selection
693 694
            if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
                 !selectionRectInternal.contains( contentsRect ) )
695 696
            {
                screenPainter.setPen( palette().active().highlight().dark(110) );
697
                screenPainter.drawRect( selectionRect );
698
            }
Enrico Ros's avatar
Enrico Ros committed
699 700 701 702
            // 3) Layer 1: give annotator painting control
            if ( d->annotator && d->annotator->routePaints( contentsRect ) )
                d->annotator->routePaint( &screenPainter, contentsRect );
            // 4) Layer 2: overlays
Piotr Szymanski's avatar
Piotr Szymanski committed
703
            if ( KpdfSettings::debugDrawBoundaries() )
704 705 706 707
            {
                screenPainter.setPen( Qt::red );
                screenPainter.drawRect( contentsRect );
            }
708
        }
709
    }
710
}
711
}
712

713 714 715
void PageView::viewportResizeEvent( QResizeEvent * event)
{
if (d->document->handleEvent( event ) )
716
{
717 718 719
    if ( d->items.isEmpty() )
        return;

720
    // start a timer that will refresh the pixmap after 0.2s
721
    if ( !d->delayResizeTimer )
722
    {
723 724
        d->delayResizeTimer = new QTimer( this );
        connect( d->delayResizeTimer, SIGNAL( timeout() ), this, SLOT( slotRelayoutPages() ) );
725
    }
726
    d->delayResizeTimer->start( 200, true );
727
}
728
}
729 730

void PageView::keyPressEvent( QKeyEvent * e )
731 732
{
if (d->document->handleEvent( e ) )
733 734
{
    e->accept();
Enrico Ros's avatar
Enrico Ros committed
735

Enrico Ros's avatar
Enrico Ros committed
736
    // if performing a selection or dyn zooming, disable keys handling
737
    if ( d->mouseSelecting || d->mouseMidZooming )
Enrico Ros's avatar
Enrico Ros committed
738 739
        return;

Enrico Ros's avatar
Enrico Ros committed
740
    // handle 'find as you type' (based on khtml/khtmlview.cpp)
Enrico Ros's avatar
Enrico Ros committed
741
    if( d->typeAheadActive )
Jakub Stachowski's avatar
Jakub Stachowski committed
742
    {
743
        // backspace: remove a char and search or terminates search
744
        if( e->key() == Qt::Key_BackSpace )
Jakub Stachowski's avatar
Jakub Stachowski committed
745
        {
746
            if( d->typeAheadString.length() > 1 )
Jakub Stachowski's avatar
Jakub Stachowski committed
747
            {
748
                d->typeAheadString = d->typeAheadString.left( d->typeAheadString.length() - 1 );
749
                bool found = d->document->searchText( PAGEVIEW_SEARCH_ID, d->typeAheadString, true, false,
750
                        KPDFDocument::NextMatch, true, qRgb( 128, 255, 128 ), true );
751 752 753
                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
754
                d->findTimeoutTimer->start( 3000, true );
Jakub Stachowski's avatar
Jakub Stachowski committed
755 756 757
            }
            else
            {
758
                slotStopFindAhead();
759
                d->document->resetSearch( PAGEVIEW_SEARCH_ID );
Jakub Stachowski's avatar
Jakub Stachowski committed
760 761
            }
        }
762
        // F3: go to next occurrency
Enrico Ros's avatar
Enrico Ros committed
763 764 765
        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
766
            d->findTimeoutTimer->stop(); // restore normal operation during possible messagebox is displayed
767 768 769
            // (1/4) it is needed to grab the keyboard becase people may have Space assigned
            // to a accel and without grabbing the keyboard you can not vim-search for space
            // because it activates the accel
Enrico Ros's avatar
Enrico Ros committed
770
            releaseKeyboard();
771
            if ( d->document->continueSearch( PAGEVIEW_SEARCH_ID ) )
Enrico Ros's avatar
Enrico Ros committed
772
                d->messageWindow->display( i18n("Text found: \"%1\".").arg(d->typeAheadString.lower()),
773
                                           PageViewMessage::Find, 3000 );
Enrico Ros's avatar
Enrico Ros committed
774
            d->findTimeoutTimer->start( 3000, true );
775 776 777
            // (2/4) it is needed to grab the keyboard becase people may have Space assigned
            // to a accel and without grabbing the keyboard you can not vim-search for space
            // because it activates the accel
Enrico Ros's avatar
Enrico Ros committed
778
            grabKeyboard();
Jakub Stachowski's avatar
Jakub Stachowski committed
779
        }
780
        // esc and return: end search
781
        else if( e->key() == Qt::Key_Escape || e->key() == Qt::Key_Return )
Jakub Stachowski's avatar
Jakub Stachowski committed
782
        {
783
            slotStopFindAhead();
Jakub Stachowski's avatar
Jakub Stachowski committed
784
        }
785
        // other key: add to text and search
786
        else if( !e->text().isEmpty() )
Jakub Stachowski's avatar
Jakub Stachowski committed
787
        {
Enrico Ros's avatar
Enrico Ros committed
788
            d->typeAheadString += e->text();
789
            bool found = d->document->searchText( PAGEVIEW_SEARCH_ID, d->typeAheadString, false, false,
790
                    KPDFDocument::NextMatch, true, qRgb( 128, 255, 128 ), true );
791 792 793
            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
794
            d->findTimeoutTimer->start( 3000, true );
Jakub Stachowski's avatar
Jakub Stachowski committed
795
        }
796
        return;
Jakub Stachowski's avatar
Jakub Stachowski committed
797
    }
798
    else if( e->key() == '/' && d->document->isOpened() && d->document->supportsSearching() )
Jakub Stachowski's avatar
Jakub Stachowski committed
799
    {
Enrico Ros's avatar
Enrico Ros committed
800 801 802 803 804 805 806 807
        // stop scrolling the page (if doing it)
        if ( d->autoScrollTimer )
        {
            d->scrollIncrement = 0;
            d->autoScrollTimer->stop();
        }
        // start type-adeas search
        d->typeAheadString = QString();
808
        d->messageWindow->display( i18n("Starting -- find text as you type"), PageViewMessage::Find, 3000 );
Enrico Ros's avatar
Enrico Ros committed
809 810
        d->typeAheadActive = true;
        if ( !d->findTimeoutTimer )
Enrico Ros's avatar
Enrico Ros committed
811 812
        {
            // create the timer on demand
Enrico Ros's avatar
Enrico Ros committed