pageview.cpp 33.1 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 6 7 8 9 10 11
 *                                                                         *
 *   With portions of code from kpdf_pagewidget.cc by:                     *
 *     Copyright (C) 2002 by Wilco Greven <greven@kde.org>                 *
 *     Copyright (C) 2003 by Christophe Devriese                           *
 *                           <Christophe.Devriese@student.kuleuven.ac.be>  *
 *     Copyright (C) 2003 by Laurent Montel <montel@kde.org>               *
 *     Copyright (C) 2003 by Dirk Mueller <mueller@kde.org>                *
 *     Copyright (C) 2004 by James Ots <kde@jamesots.com>                  *
12 13 14 15 16 17 18 19 20 21
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include <qpainter.h>
#include <qtimer.h>
#include <qpushbutton.h>
Enrico Ros's avatar
Enrico Ros committed
22 23
#include <qapplication.h>
#include <qclipboard.h>
24 25 26 27 28

#include <kiconloader.h>
#include <kurldrag.h>
#include <kaction.h>
#include <kactioncollection.h>
29
#include <kpopupmenu.h>
30 31 32 33
#include <klocale.h>
#include <kconfigbase.h>

#include <math.h>
34
#include <stdlib.h>
35 36 37 38 39 40 41 42 43 44 45 46

#include "pageview.h"
#include "pixmapwidget.h"
#include "page.h"


// structure used internally by PageView for data storage
class PageViewPrivate
{
public:
    // the document, current page and pages indices vector
    KPDFDocument * document;
47
    PageWidget * page; //equal to pages[vectorIndex]
48 49 50 51
    QValueVector< PageWidget * > pages;
    int vectorIndex;

    // view layout, zoom and mouse
52 53
    int viewColumns;
    bool viewContinous;
54 55 56 57
    PageView::ZoomMode zoomMode;
    float zoomFactor;
    PageView::MouseMode mouseMode;
    QPoint mouseGrabPos;
58
    QPoint mouseStartPos;
59
    bool mouseOnLink;
60
    PageWidget * mouseSelectionWidget;
61 62

    // other stuff
63
    QTimer *delayTimer;
64 65
    QTimer *scrollTimer;
    int scrollIncrement;
66
    bool dirtyLayout;
67 68 69 70 71 72

    // actions
    KSelectAction *aZoom;
    KToggleAction *aZoomFitWidth;
    KToggleAction *aZoomFitPage;
    KToggleAction *aZoomFitText;
73 74
    KToggleAction *aViewTwoPages;
    KToggleAction *aViewContinous;
75 76 77
};


78 79 80 81 82 83 84 85 86
/* 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..
 *  other misc functions: only slotRequestVisiblePixmaps and pickPageOnPoint noticeable,
 * and many insignificant stuff like this comment :-)
87 88
 */
PageView::PageView( QWidget *parent, KPDFDocument *document )
89
    : QScrollView( parent, "KPDF::pageView", WNoAutoErase | WStaticContents )
90 91 92 93 94
{
    // create and initialize private storage structure
    d = new PageViewPrivate();
    d->document = document;
    d->page = 0;
95
    d->vectorIndex = -1;
96 97
    d->viewColumns = 1;
    d->viewContinous = false;
98
    d->zoomMode = ZoomFixed;
99
    d->zoomFactor = 1.0;
100 101
    d->mouseMode = MouseNormal;
    d->mouseOnLink = false;
102
    d->mouseSelectionWidget = 0;
103
    d->delayTimer = 0;
104 105
    d->scrollTimer = 0;
    d->scrollIncrement = 0;
106
    d->dirtyLayout = false;
107 108 109

    // dealing with (very) large areas so enable clipper
    enableClipper( true );
110

111 112 113 114 115
    // widget setup: setup focus, accept drops and track mouse
    viewport()->setFocusProxy( this );
    viewport()->setFocusPolicy( StrongFocus );
    viewport()->setPaletteBackgroundColor( Qt::gray );
    setResizePolicy( Manual );
116
    setAcceptDrops( true );
117
    setDragAutoScroll( false );
118
    viewport()->setMouseTracking( true );
119 120 121

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

    // set a corner button to resize the view to the page size
    QPushButton * resizeButton = new QPushButton( viewport() );
125
    resizeButton->setPixmap( SmallIcon("crop") );
126 127 128 129 130 131 132 133 134 135 136 137
    setCornerWidget( resizeButton );
    resizeButton->setEnabled( false );
    // connect(...);
}

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

void PageView::setupActions( KActionCollection * ac, KConfigGroup * config )
{
138
    // Zoom actions ( higher scales takes lots of memory! )
139
    d->aZoom = new KSelectAction( i18n( "Zoom" ), "viewmag", 0, this, SLOT( slotZoom() ), ac, "zoom_to" );
140 141
    d->aZoom->setEditable( true );
    updateZoomText();
142 143 144 145 146 147 148 149 150 151 152

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

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

156
    // View-Layout actions
157 158 159 160
    d->aViewTwoPages = new KToggleAction( i18n("Two Pages"), "view_left_right", 0, ac, "view_twopages" );
    connect( d->aViewTwoPages, SIGNAL( toggled( bool ) ), SLOT( slotTwoPagesToggled( bool ) ) );
    d->aViewTwoPages->setChecked( config->readBoolEntry( "ViewTwoPages", false ) );
    slotTwoPagesToggled( d->aViewTwoPages->isChecked() );
161

162 163 164 165
    d->aViewContinous = new KToggleAction( i18n("Continous"), "view_text", 0, ac, "view_continous" );
    connect( d->aViewContinous, SIGNAL( toggled( bool ) ), SLOT( slotContinousToggled( bool ) ) );
    d->aViewContinous->setChecked( config->readBoolEntry( "ViewContinous", true ) );
    slotContinousToggled( d->aViewContinous->isChecked() );
166 167

    // Mouse-Mode actions
168
    KToggleAction * mn = new KRadioAction( i18n("Normal"), "mouse", 0, this, SLOT( slotSetMouseNormal() ), ac, "mouse_drag" );
169 170 171
    mn->setExclusiveGroup("MouseType");
    mn->setChecked( true );

172
    KToggleAction * ms = new KRadioAction( i18n("Select"), "frame_edit", 0, this, SLOT( slotSetMouseSelect() ), ac, "mouse_select" );
173
    ms->setExclusiveGroup("MouseType");
174
    //ms->setEnabled( false ); // implement feature before removing this line
175

176
    KToggleAction * md = new KRadioAction( i18n("Draw"), "edit", 0, this, SLOT( slotSetMouseDraw() ), ac, "mouse_draw" );
177
    md->setExclusiveGroup("MouseType");
178
    //md->setEnabled( false ); // implement feature before removing this line
179 180

    // Other actions
181 182 183 184 185 186
    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" );

187 188 189 190 191 192 193 194 195 196 197
    KToggleAction * ss = new KToggleAction( i18n( "Show &Scrollbars" ), 0, ac, "show_scrollbars" );
    ss->setCheckedState(i18n("Hide &Scrollbars"));
    connect( ss, SIGNAL( toggled( bool ) ), SLOT( slotToggleScrollBars( bool ) ) );

    ss->setChecked( config->readBoolEntry( "ShowScrollBars", true ) );
    slotToggleScrollBars( ss->isChecked() );
}

void PageView::saveSettings( KConfigGroup * config )
{
    config->writeEntry( "ShowScrollBars", hScrollBarMode() == AlwaysOn );
198 199
    config->writeEntry( "ViewTwoPages", d->aViewTwoPages->isChecked() );
    config->writeEntry( "ViewContinous", d->aViewContinous->isChecked() );
200 201 202
}


203
//BEGIN KPDFDocumentObserver inherited methods
Enrico Ros's avatar
Enrico Ros committed
204 205 206 207 208 209 210 211 212 213 214 215 216 217
void PageView::pageSetup( const QValueVector<KPDFPage*> & pageSet, bool documentChanged )
{
    // reuse current pages if nothing new
    if ( ( pageSet.count() == d->pages.count() ) && !documentChanged )
    {
        int count = pageSet.count();
        for ( int i = 0; (i < count) && !documentChanged; i++ )
            if ( (int)pageSet[i]->number() != d->pages[i]->pageNumber() )
                documentChanged = true;
        if ( !documentChanged )
            return;
    }

    // delete all widgets (one for each page in pageSet)
218 219 220 221 222 223 224 225 226 227
    QValueVector< PageWidget * >::iterator dIt = d->pages.begin(), dEnd = d->pages.end();
    for ( ; dIt != dEnd; ++dIt )
        delete *dIt;
    d->pages.clear();
    d->page = 0;

    // create children widgets
    QValueVector< KPDFPage * >::const_iterator setIt = pageSet.begin(), setEnd = pageSet.end();
    for ( ; setIt != setEnd; ++setIt )
    {
228 229
        PageWidget * p = new PageWidget( viewport(), *setIt );
        p->setFocusProxy( this );
230
        p->setMouseTracking( true );
231 232 233
        d->pages.push_back( p );
    }

234
    // invalidate layout
235
    d->dirtyLayout = true;
236 237
}

Enrico Ros's avatar
Enrico Ros committed
238
void PageView::pageSetCurrent( int pageNumber, const QRect & /*viewport*/ )
239
{
240 241 242
    // select next page
    d->vectorIndex = 0;
    d->page = 0;
243 244 245
    QValueVector< PageWidget * >::iterator pIt = d->pages.begin(), pEnd = d->pages.end();
    for ( ; pIt != pEnd; ++pIt )
    {
246
        if ( (*pIt)->pageNumber() == pageNumber )
247
        {
248
            d->page = *pIt;
249 250
            break;
        }
251
        d->vectorIndex ++;
252
    }
253 254
    if ( !d->page )
        return;
255

Enrico Ros's avatar
Enrico Ros committed
256 257
    // relayout in "Single Pages" mode or if a relayout is pending
    if ( !d->viewContinous || d->dirtyLayout )
258 259 260
        slotRelayoutPages();

    // center the view to see the selected page
Enrico Ros's avatar
Enrico Ros committed
261
    // FIXME take care of viewport
262
    int xPos = childX( d->page ) + d->page->widthHint() / 2,
Enrico Ros's avatar
Enrico Ros committed
263
        yPos = childY( d->page );
264 265 266 267 268 269
    center( xPos, yPos + visibleHeight() / 2 - 10 );
    slotRequestVisiblePixmaps();

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

272
void PageView::notifyPixmapChanged( int pageNumber )
273
{
274 275 276
    QValueVector< PageWidget * >::iterator pIt = d->pages.begin(), pEnd = d->pages.end();
    for ( ; pIt != pEnd; ++pIt )
        if ( (*pIt)->pageNumber() == pageNumber )
277
        {
278
            (*pIt)->update();
279 280 281 282 283
            break;
        }
}
//END KPDFDocumentObserver inherited methods

284
//BEGIN widget events
285 286
void PageView::contentsMousePressEvent( QMouseEvent * e )
{
287 288
    bool leftButton = e->button() & LeftButton,
         rightButton = e->button() & RightButton;
289 290
    switch ( d->mouseMode )
    {
291 292
    case MouseNormal:    // drag start / click / link following
        if ( leftButton )
293
        {
294
            d->mouseStartPos = e->globalPos();
295
            if ( d->mouseOnLink )
296 297 298 299 300 301
                d->mouseGrabPos = QPoint();
            else
            {
                d->mouseGrabPos = d->mouseStartPos;
                setCursor( sizeAllCursor );
            }
302
        }
303
        else if ( rightButton )
304 305 306
            emit rightClick();
        break;

307 308 309 310 311 312 313 314 315 316 317 318 319
    case MouseSelection: // set first corner of the selection rect
        if ( leftButton )
        {
            if ( d->mouseSelectionWidget )
                d->mouseSelectionWidget->clearSelection();
            d->mouseSelectionWidget = 0;
            PageWidget * page = pickPageOnPoint( e->x(), e->y() );
            if ( page )
            {
                page->setBeginCorner( e->x() - childX( page ), e->y() - childY( page ) );
                d->mouseSelectionWidget = page;
            }
        }
320
        break;
321 322 323 324 325 326

    case MouseEdit:      // ? place the beginning of [tool] ?
        break;
    }
}

327
void PageView::contentsMouseReleaseEvent( QMouseEvent * e )
328
{
329 330
    bool leftButton = e->button() & LeftButton,
         rightButton = e->button() & RightButton;
331
    PageWidget * pageWidget = pickPageOnPoint( e->x(), e->y() );
332 333 334
    switch ( d->mouseMode )
    {
    case MouseNormal:    // end drag / follow link
335
        if ( leftButton )
336 337
        {
            setCursor( arrowCursor );
338
            // check if over a link
339
            if ( d->mouseOnLink && pageWidget )
340
            {
341 342 343
                int linkX = e->x() - childX( pageWidget ),
                    linkY = e->y() - childY( pageWidget );
                d->document->slotProcessLink( pageWidget->page()->getLink( linkX, linkY ) );
344
            }
345
            // check if it was a click, in that case select the page
346 347
            else if ( e->globalPos() == d->mouseStartPos && pageWidget )
                d->document->slotSetCurrentPage( pageWidget->pageNumber() );
348 349 350
            // check wether to restore the hand cursor
            else if ( d->mouseOnLink )
                setCursor( pointingHandCursor );
351
        }
352
        else if ( rightButton && pageWidget )
353 354
        {
            // If over a page display a popup menu
355
            const KPDFPage * kpdfPage = pageWidget->page();
356
            KPopupMenu * m_popup = new KPopupMenu( this, "rmb popup" );
357
            m_popup->insertTitle( i18n( "Page %1" ).arg( kpdfPage->number() + 1 ) );
358 359 360 361 362 363
            if ( kpdfPage->isBookmarked() )
                m_popup->insertItem( SmallIcon("bookmark"), i18n("Remove Bookmark"), 1 );
            else
                m_popup->insertItem( SmallIcon("bookmark"), i18n("Add Bookmark"), 1 );
            m_popup->insertItem( SmallIcon("viewmagfit"), i18n("Fit Page"), 2 );
            m_popup->insertItem( SmallIcon("pencil"), i18n("Edit"), 3 );
364
            switch ( m_popup->exec(e->globalPos()) )
365 366
            {
            case 1:
367
                d->document->slotBookmarkPage( kpdfPage->number(), !kpdfPage->isBookmarked() );
368
                break;
369 370 371 372
            case 2: // FIXME less hackish, please!
                d->aZoomFitWidth->setChecked( true );
                updateZoom( ZoomFitWidth );
                d->aViewTwoPages->setChecked( false );
373
                slotTwoPagesToggled( false );
374
                d->document->slotSetCurrentPage( kpdfPage->number() );
375
                break;
376 377
            case 3: // TODO switch to edit mode
                slotSetMouseDraw();
378 379 380
                break;
            }
        }
381 382
        break;

383 384 385
    case MouseSelection: // get text from the page
        if ( leftButton && d->mouseSelectionWidget )
        {
Enrico Ros's avatar
Enrico Ros committed
386
            // request the textpage if there isn't one
387 388 389
            const KPDFPage * kpdfPage = d->mouseSelectionWidget->page();
            if ( !kpdfPage->hasSearchPage() )
                d->document->requestTextPage( kpdfPage->number() );
Enrico Ros's avatar
Enrico Ros committed
390 391 392 393 394 395 396
            // copy text into the clipboard
            QClipboard *cb = QApplication::clipboard();
            const QString & selection = d->mouseSelectionWidget->selectedText();
            cb->setText( selection, QClipboard::Clipboard );
            if ( cb->supportsSelection() )
                cb->setText( selection, QClipboard::Selection );
            // clear widget selection
397 398 399 400
            d->mouseSelectionWidget->clearSelection();
            d->mouseSelectionWidget = 0;
        }
        break;
401 402 403 404 405 406 407 408

    case MouseEdit:      // ? apply [tool] ?
        break;
    }
}

void PageView::contentsMouseMoveEvent( QMouseEvent * e )
{
409
    bool leftButton = e->state() & LeftButton;
410 411
    switch ( d->mouseMode )
    {
412
    case MouseNormal:    // drag page / change mouse cursor if over links
413
        if ( leftButton && !d->mouseGrabPos.isNull() )
414 415 416 417 418
        {
            QPoint delta = d->mouseGrabPos - e->globalPos();
            scrollBy( delta.x(), delta.y() );
            d->mouseGrabPos = e->globalPos();
        }
419
        else
420 421
        {
            // set cursor only when entering / leaving (setCursor has not an internal cache)
422
            PageWidget * pageWidget = pickPageOnPoint( e->x(), e->y() );
423 424 425 426
            if ( !pageWidget )
                break;
            bool onLink = pageWidget->page()->hasLink( e->x() - childX( pageWidget ), e->y() - childY( pageWidget ) );
            if ( onLink != d->mouseOnLink )
427
            {
428 429
                d->mouseOnLink = onLink;
                setCursor( onLink ? pointingHandCursor : arrowCursor );
430 431 432 433
            }
        }
        break;

434 435 436 437 438 439 440
    case MouseSelection: // set selection's second corner
        if ( leftButton && d->mouseSelectionWidget )
            // continue selecting on current page
            d->mouseSelectionWidget->setEndCorner(
                e->x() - childX( d->mouseSelectionWidget ),
                e->y() - childY( d->mouseSelectionWidget ) );
        break;
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468

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

void PageView::keyPressEvent( QKeyEvent * e )
{
    switch ( e->key() )
    {
    case Key_Up:
        if ( atTop() )
            scrollUp();
        else
            verticalScrollBar()->subtractLine();
        break;
    case Key_Down:
        if ( atBottom() )
            scrollDown();
        else
            verticalScrollBar()->addLine();
        break;
    case Key_Left:
        horizontalScrollBar()->subtractLine();
        break;
    case Key_Right:
        horizontalScrollBar()->addLine();
        break;
469 470 471 472 473
    case Key_PageUp:
        verticalScrollBar()->subtractPage();
        break;
    case Key_PageDown:
        verticalScrollBar()->addPage();
474
        break;
475 476 477 478 479 480 481 482 483 484 485
    case Key_Shift:
    case Key_Control:
        if ( d->scrollTimer )
        {
            if ( d->scrollTimer->isActive() )
                d->scrollTimer->stop();
            else
                slotAutoScoll();
            e->accept();
            return;
        }
486 487 488 489 490
    default:
        e->ignore();
        return;
    }
    e->accept();
491 492 493 494 495 496

    if ( d->scrollTimer )
    {
        d->scrollIncrement = 0;
        d->scrollTimer->stop();
    }
497 498 499 500 501 502 503 504 505 506 507 508
}

void PageView::wheelEvent( QWheelEvent *e )
{
    int delta = e->delta();
    e->accept();
    if ( (e->state() & ControlButton) == ControlButton ) {
        if ( e->delta() > 0 )
            slotZoomOut();
        else
            slotZoomIn();
    }
509
    else if ( delta <= -120 && atBottom() && !d->viewContinous )
510
        scrollDown();
511
    else if ( delta >= 120 && atTop() && !d->viewContinous )
512 513 514 515 516
        scrollUp();
    else
        QScrollView::wheelEvent( e );
}

517 518 519 520 521 522 523 524 525 526 527
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 );
}

528 529 530 531 532 533 534 535 536 537 538 539 540 541
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

//BEGIN internal SLOTS
542
void PageView::slotZoom()
543
{
544
    updateZoom( ZoomFixed );
545 546 547 548
}

void PageView::slotZoomIn()
{
549
    updateZoom( ZoomIn );
550 551 552 553
}

void PageView::slotZoomOut()
{
554
    updateZoom( ZoomOut );
555 556 557 558
}

void PageView::slotFitToWidthToggled( bool on )
{
559
    if ( on ) updateZoom( ZoomFitWidth );
560 561 562 563
}

void PageView::slotFitToPageToggled( bool on )
{
564
    if ( on ) updateZoom( ZoomFitPage );
565 566 567 568
}

void PageView::slotFitToTextToggled( bool on )
{
569
    if ( on ) updateZoom( ZoomFitText );
570 571
}

572 573
void PageView::slotTwoPagesToggled( bool on )
{
574 575 576 577
    int newColumns = on ? 2 : 1;
    if ( d->viewColumns != newColumns )
    {
        d->viewColumns = newColumns;
578
        slotRelayoutPages();
579
    }
580 581
}

582 583
void PageView::slotContinousToggled( bool on )
{
584 585 586
    if ( d->viewContinous != on )
    {
        d->viewContinous = on;
587
        slotRelayoutPages();
588
    }
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
}

void PageView::slotSetMouseNormal()
{
    d->mouseMode = MouseNormal;
}

void PageView::slotSetMouseSelect()
{
    d->mouseMode = MouseSelection;
}

void PageView::slotSetMouseDraw()
{
    d->mouseMode = MouseEdit;
}

606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
void PageView::slotScrollUp()
{
    if ( d->scrollIncrement < -9 )
        return;
    d->scrollIncrement--;
    slotAutoScoll();
}

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

622 623 624 625 626 627
void PageView::slotToggleScrollBars( bool on )
{
    setHScrollBarMode( on ? AlwaysOn : AlwaysOff );
    setVScrollBarMode( on ? AlwaysOn : AlwaysOff );
}

628 629
void PageView::slotRelayoutPages()
// called by: pageSetup, viewportResizeEvent, slotTwoPagesToggled, slotContinousToggled, updateZoom
630 631 632 633 634 635 636 637 638 639
{
    // set an empty container if we have no pages
    int pageCount = d->pages.count();
    if ( pageCount < 1 )
    {
        resizeContents( 0,0 );
        return;
    }

    int viewportWidth = clipper()->width(),
640 641 642
        viewportHeight = clipper()->height(),
        fullWidth = 0,
        fullHeight = 0;
643 644

    if ( d->viewContinous == TRUE )
645
    {
646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
        // Here we find out column's width and row's height to compute a table
        // so we can place widgets 'centered in virtual cells'.
        int nCols = d->viewColumns,
            nRows = (int)ceilf( (float)pageCount / (float)nCols ),
            * colWidth = new int[ nCols ],
            * rowHeight = new int[ nRows ],
            cIdx = 0,
            rIdx = 0;
        for ( int i = 0; i < nCols; i++ )
            colWidth[ i ] = viewportWidth / nCols;
        for ( int i = 0; i < nRows; i++ )
            rowHeight[ i ] = 0;

        // 1) find the maximum columns width and rows height for a grid in
        // which each page must well-fit inside a cell
        QValueVector< PageWidget * >::iterator pIt = d->pages.begin(), pEnd = d->pages.end();
        for ( ; pIt != pEnd; ++pIt )
663
        {
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
            PageWidget * p = *pIt;
            // update internal page geometry
            if ( d->zoomMode == ZoomFixed )
                p->setZoomFixed( d->zoomFactor );
            else if ( d->zoomMode == ZoomFitWidth )
                p->setZoomFitWidth( colWidth[ cIdx ] - 10 );
            else
                p->setZoomFitRect( colWidth[ cIdx ] - 10, viewportHeight - 10 );
            // find row's maximum height and column's max width
            int pWidth = p->widthHint(),
                pHeight = p->heightHint();
            if ( pWidth > colWidth[ cIdx ] )
                colWidth[ cIdx ] = pWidth;
            if ( pHeight > rowHeight[ rIdx ] )
                rowHeight[ rIdx ] = pHeight;
            // update col/row indices
            if ( ++cIdx == nCols )
681
            {
682 683
                cIdx = 0;
                rIdx++;
684 685
            }
        }
686 687 688

        // 2) arrange widgets inside cells
        int insertX = 0,
689
            insertY = (int)(2.0 + 4.0 * d->zoomFactor);
690 691 692 693 694 695 696 697 698 699 700 701
        cIdx = 0;
        rIdx = 0;
        for ( pIt = d->pages.begin(); pIt != pEnd; ++pIt )
        {
            PageWidget * p = *pIt;
            int pWidth = p->widthHint(),
                pHeight = p->heightHint(),
                cWidth = colWidth[ cIdx ],
                rHeight = rowHeight[ rIdx ];
            // show, resize and center widget inside 'cells'
            p->resize( pWidth, pHeight );
            moveChild( p, insertX + (cWidth - pWidth) / 2,
702
                       insertY + (rHeight - pHeight) / 2 );
703 704 705 706 707 708 709 710 711 712 713 714
            p->show();
            // advance col/row index
            insertX += cWidth;
            if ( ++cIdx == nCols )
            {
                cIdx = 0;
                rIdx++;
                insertX = 0;
                insertY += rHeight + (int)(5.0 + 15.0 * d->zoomFactor);
            }
        }

715
        fullHeight = cIdx ? (insertY + rowHeight[ rIdx ] + 10) : insertY;
716 717 718 719 720 721 722 723
        for ( int i = 0; i < nCols; i++ )
            fullWidth += colWidth[ i ];

        delete [] colWidth;
        delete [] rowHeight;
    }
    else // viewContinous is FALSE
    {
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
        PageWidget * currentPage = d->page ? d->page : d->pages[0];

        // setup varialbles for a 1(row) x N(columns) grid
        int nCols = d->viewColumns,
            * colWidth = new int[ nCols ],
            cIdx = 0;
        fullHeight = viewportHeight;
        for ( int i = 0; i < nCols; i++ )
            colWidth[ i ] = viewportWidth / nCols;

        // 1) find out maximum area extension for the pages
        QValueVector< PageWidget * >::iterator pIt = d->pages.begin(), pEnd = d->pages.end();
        for ( ; pIt != pEnd; ++pIt )
        {
            PageWidget * p = *pIt;
            if ( p == currentPage || (cIdx > 0 && cIdx < nCols) )
            {
                if ( d->zoomMode == ZoomFixed )
                    p->setZoomFixed( d->zoomFactor );
                else if ( d->zoomMode == ZoomFitWidth )
                    p->setZoomFitWidth( colWidth[ cIdx ] - 10 );
                else
                    p->setZoomFitRect( colWidth[ cIdx ] - 10, viewportHeight - 10 );
                if ( p->widthHint() > colWidth[ cIdx ] )
                    colWidth[ cIdx ] = p->widthHint();
                fullHeight = QMAX( fullHeight, p->heightHint() );
                cIdx++;
            }
        }

        // 2) hide all widgets except the displayable ones and dispose those
        int insertX = 0,
            insertY = (int)(2.0 + 4.0 * d->zoomFactor);
        cIdx = 0;
        for ( pIt = d->pages.begin(); pIt != pEnd; ++pIt )
        {
            PageWidget * p = *pIt;
            if ( p == currentPage || (cIdx > 0 && cIdx < nCols) )
            {
                int pWidth = p->widthHint(),
                    pHeight = p->heightHint();
                // show, resize and center widget inside 'cells'
                p->resize( pWidth, pHeight );
                moveChild( p, insertX + (colWidth[ cIdx ] - pWidth) / 2,
                           insertY + (fullHeight - pHeight) / 2 );
                p->show();
                // advance col/row index
                insertX += colWidth[ cIdx ];
                cIdx++;
            }
            else
                p->hide();
        }

        for ( int i = 0; i < nCols; i++ )
            fullWidth += colWidth[ i ];

        delete [] colWidth;
    }

    // 3) update scrollview's contents size and recenter view
    int oldWidth = contentsWidth(),
        oldHeight = contentsHeight();
    if ( oldWidth != fullWidth || oldHeight != fullHeight )
    {
        resizeContents( fullWidth, fullHeight );
        if ( oldWidth > 0 && oldHeight > 0 )
            center( fullWidth * (contentsX() + visibleWidth() / 2) / oldWidth,
                    fullHeight * (contentsY() + visibleHeight() / 2) / oldHeight );
        else
            center( fullWidth / 2, 0 );
795
    }
796 797

    // reset dirty state
798 799 800
    d->dirtyLayout = false;
}

801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
void PageView::slotRequestVisiblePixmaps( int newLeft, int newTop )
{
    // precalc view limits for intersecting with page coords inside the lOOp
    int vLeft = (newLeft == -1) ? contentsX() : newLeft,
        vRight = vLeft + visibleWidth(),
        vTop = (newTop == -1) ? contentsY() : newTop,
        vBottom = vTop + visibleHeight();

    // scroll from the top to the last visible thumbnail
    QValueVector< PageWidget * >::iterator pIt = d->pages.begin(), pEnd = d->pages.end();
    for ( ; pIt != pEnd; ++pIt )
    {
        PageWidget * p = *pIt;
        int pLeft = childX( p ),
            pRight = pLeft + p->widthHint(),
            pTop = childY( p ),
            pBottom = pTop + p->heightHint();
        if ( p->isShown() && pRight > vLeft && pLeft < vRight && pBottom > vTop && pTop < vBottom )
            d->document->requestPixmap( PAGEVIEW_ID, p->pageNumber(), p->pixmapWidth(), p->pixmapHeight(), true );
    }
}
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845

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

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

    // compute delay between timer ticks and scroll amount per tick
    int index = abs( d->scrollIncrement ) - 1;  // 0..9
    const int scrollDelay[10] =  { 200, 100, 50, 30, 20, 30, 25, 20, 30, 20 };
    const int scrollOffset[10] = {   1,   1,  1,  1,  1,  2,  2,  2,  4,  4 };
    d->scrollTimer->changeInterval( scrollDelay[ index ] );
    scrollBy( 0, d->scrollIncrement > 0 ? scrollOffset[ index ] : -scrollOffset[ index ] );
}
846 847
//END internal SLOTS

848 849 850 851
void PageView::updateZoom( ZoomMode newZoomMode )
{
    if ( newZoomMode == ZoomFixed )
    {
852
        if ( d->aZoom->currentItem() == 0 )
853
            newZoomMode = ZoomFitWidth;
854
        else if ( d->aZoom->currentItem() == 1 )
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
            newZoomMode = ZoomFitPage;
    }

    float newFactor = d->zoomFactor;
    KAction * checkedZoomAction = 0;
    switch ( newZoomMode )
    {
    case ZoomFixed:{
        QString z = d->aZoom->currentText();
        newFactor = KGlobal::locale()->readNumber( z.remove( z.find( '%' ), 1 ) ) / 100.0;
        if ( newFactor < 0.1 || newFactor > 8.0 )
            return;
        }break;
    case ZoomIn:
        newFactor += 0.1;
        if ( newFactor >= 4.0 )
            newFactor = 4.0;
        newZoomMode = ZoomFixed;
        break;
    case ZoomOut:
        newFactor -= 0.1;
        if ( newFactor <= 0.125 )
            newFactor = 0.125;
        newZoomMode = ZoomFixed;
        break;
    case ZoomFitWidth:
        checkedZoomAction = d->aZoomFitWidth;
        break;
    case ZoomFitPage:
        checkedZoomAction = d->aZoomFitPage;
        break;
    case ZoomFitText:
        checkedZoomAction = d->aZoomFitText;
        break;
    }

    if ( newZoomMode != d->zoomMode || (newZoomMode == ZoomFixed && newFactor != d->zoomFactor ) )
    {
        // rebuild layout and change the zoom selectAction contents
        d->zoomMode = newZoomMode;
        d->zoomFactor = newFactor;
        slotRelayoutPages();
        updateZoomText();
898
        // update actions checked state
899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949
        d->aZoomFitWidth->setChecked( checkedZoomAction == d->aZoomFitWidth );
        d->aZoomFitPage->setChecked( checkedZoomAction == d->aZoomFitPage );
        d->aZoomFitText->setChecked( checkedZoomAction == d->aZoomFitText );
        // request pixmaps
        slotRequestVisiblePixmaps();
    }
}

void PageView::updateZoomText()
{
    // use current page zoom as zoomFactor if in ZoomFit/* mode
    if ( d->zoomMode != ZoomFixed && d->pages.count() > 0 )
        d->zoomFactor = d->page ? d->page->zoomFactor() : d->pages[0]->zoomFactor();
    float newFactor = d->zoomFactor;
    d->aZoom->clear();

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

    // add percent items
    QString double_oh( "00" );
    const float zoomValue[10] = { 0.125, 0.25, 0.333, 0.5, 0.667, 0.75, 1, 1.25, 1.50, 2 };
    int idx = 0,
        selIdx = 2; // use 3 if "fit text" present
    bool inserted = false; //use: "d->zoomMode != ZoomFixed" to hide Fit/* zoom ratio
    while ( idx < 10 || !inserted )
    {
        float value = idx < 10 ? zoomValue[ idx ] : newFactor;
        if ( !inserted && newFactor < (value - 0.001) )
            value = newFactor;
        else
            idx ++;
        if ( value > (newFactor - 0.001) && value < (newFactor + 0.001) )
            inserted = true;
        if ( !inserted )
            selIdx++;
        QString localValue( KGlobal::locale()->formatNumber( value * 100.0, 2 ) );
        localValue.remove( KGlobal::locale()->decimalSymbol() + double_oh );
        translated << QString( "%1%" ).arg( localValue );
    }
    d->aZoom->setItems( translated );

    // select current item in list
    if ( d->zoomMode == ZoomFitWidth )
        selIdx = 0;
    else if ( d->zoomMode == ZoomFitPage )
        selIdx = 1;
    else if ( d->zoomMode == ZoomFitText )
        selIdx = 2;
    d->aZoom->setCurrentItem( selIdx );
950 951
}

952 953 954 955 956 957 958 959 960 961 962 963
PageWidget * PageView::pickPageOnPoint( int x, int y )
{
    PageWidget * page = 0;
    QValueVector< PageWidget * >::iterator pIt = d->pages.begin(), pEnd = d->pages.end();
    for ( ; pIt != pEnd; ++pIt )
    {
        PageWidget * p = *pIt;
        int pLeft = childX( p ),
            pRight = pLeft + p->widthHint(),
            pTop = childY( p ),
            pBottom = pTop + p->heightHint();
        // little optimized, stops if found or probably quits on the next row
964
        if ( x > pLeft && x < pRight && y < pBottom && p->isShown() )
965 966 967 968 969 970 971 972 973
        {
            if ( y > pTop )
                page = p;
            break;
        }
    }
    return page;
}

974 975 976 977 978 979 980 981 982 983 984 985 986 987
bool PageView::atTop() const
{
    return verticalScrollBar()->value() == verticalScrollBar()->minValue();
}

bool PageView::atBottom() const
{
    return verticalScrollBar()->value() == verticalScrollBar()->maxValue();
}

void PageView::scrollUp()
{
    if( atTop() && d->vectorIndex > 0 )
        // go to the bottom of previous page
Enrico Ros's avatar
Enrico Ros committed
988
        d->document->slotSetCurrentPage( d->pages[ d->vectorIndex - 1 ]->pageNumber() ); //TODO add position
989 990 991 992 993 994 995 996 997 998 999 1000
    else
    {   // go towards the top of current page
        int newValue = QMAX( verticalScrollBar()->value() - height() + 50,
                             verticalScrollBar()->minValue() );
        verticalScrollBar()->setValue( newValue );
    }
}

void PageView::scrollDown()
{
    if( atBottom() && d->vectorIndex < (int)d->pages.count() - 1 )
        // go to the top of previous page
Enrico Ros's avatar
Enrico Ros committed
1001
        d->document->slotSetCurrentPage( d->pages[ d->vectorIndex + 1 ]->pageNumber() ); // TODO add position
1002 1003 1004 1005 1006 1007 1008 1009 1010
    else
    {    // go towards the bottom of current page
        int newValue = QMIN( verticalScrollBar()->value() + height() - 50,
                             verticalScrollBar()->maxValue() );
        verticalScrollBar()->setValue( newValue );
    }
}

#include "pageview.moc"