add a small widget for showing an animation from the "animations" category of the...
[kdegraphics.git] / okular / ui / pageview.cpp
blob3f81762cd6378ed78ec404d90219a328f1ff33fc
1 /***************************************************************************
2 * Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> *
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 * Copyright (C) 2002 by Wilco Greven <greven@kde.org> *
7 * Copyright (C) 2003 by Christophe Devriese *
8 * <Christophe.Devriese@student.kuleuven.ac.be> *
9 * Copyright (C) 2003 by Laurent Montel <montel@kde.org> *
10 * Copyright (C) 2003 by Dirk Mueller <mueller@kde.org> *
11 * Copyright (C) 2004 by James Ots <kde@jamesots.com> *
12 * *
13 * This program is free software; you can redistribute it and/or modify *
14 * it under the terms of the GNU General Public License as published by *
15 * the Free Software Foundation; either version 2 of the License, or *
16 * (at your option) any later version. *
17 ***************************************************************************/
19 #include "pageview.h"
21 // qt/kde includes
22 #include <qcursor.h>
23 #include <qevent.h>
24 #include <qimage.h>
25 #include <qpainter.h>
26 #include <qtimer.h>
27 #include <qset.h>
28 #include <qscrollbar.h>
29 #include <qtooltip.h>
30 #include <qapplication.h>
31 #include <qclipboard.h>
33 #include <kaction.h>
34 #include <kactionmenu.h>
35 #include <kstandardaction.h>
36 #include <kactioncollection.h>
37 #include <kmenu.h>
38 #include <klocale.h>
39 #include <kfiledialog.h>
40 #include <kglobal.h>
41 #include <kglobalsettings.h>
42 #include <kselectaction.h>
43 #include <ktoggleaction.h>
44 #include <kdebug.h>
45 #include <kmessagebox.h>
46 #include <kicon.h>
48 // system includes
49 #include <math.h>
50 #include <stdlib.h>
52 // local includes
53 #include "formwidgets.h"
54 #include "pageviewutils.h"
55 #include "pagepainter.h"
56 #include "core/annotations.h"
57 #include "annotwindow.h"
58 #include "guiutils.h"
59 #include "annotationpopup.h"
60 #include "pageviewannotator.h"
61 #include "toolaction.h"
62 #include "tts.h"
63 #include "videowidget.h"
64 #include "core/action.h"
65 #include "core/document.h"
66 #include "core/form.h"
67 #include "core/page.h"
68 #include "core/misc.h"
69 #include "core/generator.h"
70 #include "core/movie.h"
71 #include "settings.h"
73 static int pageflags = PagePainter::Accessibility | PagePainter::EnhanceLinks |
74 PagePainter::EnhanceImages | PagePainter::Highlights |
75 PagePainter::TextSelection | PagePainter::Annotations;
77 // structure used internally by PageView for data storage
78 class PageViewPrivate
80 public:
81 PageViewPrivate( PageView *qq );
83 FormWidgetsController* formWidgetsController();
84 OkularTTS* tts();
85 QString selectedText() const;
87 // the document, pageviewItems and the 'visible cache'
88 PageView *q;
89 Okular::Document * document;
90 QVector< PageViewItem * > items;
91 QLinkedList< PageViewItem * > visibleItems;
93 // view layout (columns and continuous in Settings), zoom and mouse
94 PageView::ZoomMode zoomMode;
95 float zoomFactor;
96 PageView::MouseMode mouseMode;
97 QPoint mouseGrabPos;
98 QPoint mousePressPos;
99 QPoint mouseSelectPos;
100 bool mouseMidZooming;
101 int mouseMidLastY;
102 bool mouseSelecting;
103 QRect mouseSelectionRect;
104 QColor mouseSelectionColor;
105 bool mouseTextSelecting;
106 QSet< int > pagesWithTextSelection;
107 bool mouseOnRect;
108 Okular::Annotation * mouseAnn;
109 QPoint mouseAnnPos;
111 // viewport move
112 bool viewportMoveActive;
113 QTime viewportMoveTime;
114 QPoint viewportMoveDest;
115 QTimer * viewportMoveTimer;
116 // auto scroll
117 int scrollIncrement;
118 QTimer * autoScrollTimer;
119 // annotations
120 PageViewAnnotator * annotator;
121 //text annotation dialogs list
122 QHash< Okular::Annotation *, AnnotWindow * > m_annowindows;
123 // other stuff
124 QTimer * delayResizeTimer;
125 bool dirtyLayout;
126 bool blockViewport; // prevents changes to viewport
127 bool blockPixmapsRequest; // prevent pixmap requests
128 PageViewMessage * messageWindow; // in pageviewutils.h
129 bool m_formsVisible;
130 FormWidgetsController *formsWidgetController;
131 OkularTTS * m_tts;
132 QTimer * refreshTimer;
133 int refreshPage;
135 // infinite resizing loop prevention
136 bool bothScrollbarsVisible;
138 // drag scroll
139 QPoint dragScrollVector;
140 QTimer dragScrollTimer;
142 // actions
143 KAction * aRotateClockwise;
144 KAction * aRotateCounterClockwise;
145 KAction * aRotateOriginal;
146 KSelectAction * aPageSizes;
147 KToggleAction * aTrimMargins;
148 KAction * aMouseNormal;
149 KAction * aMouseSelect;
150 KAction * aMouseTextSelect;
151 KToggleAction * aToggleAnnotator;
152 KSelectAction * aZoom;
153 KAction * aZoomIn;
154 KAction * aZoomOut;
155 KToggleAction * aZoomFitWidth;
156 KToggleAction * aZoomFitPage;
157 KToggleAction * aZoomFitText;
158 KActionMenu * aViewMode;
159 KToggleAction * aViewContinuous;
160 QAction * aPrevAction;
161 KAction * aToggleForms;
162 KAction * aSpeakDoc;
163 KAction * aSpeakPage;
164 KAction * aSpeakStop;
165 KActionCollection * actionCollection;
167 int setting_viewMode;
168 int setting_viewCols;
169 bool setting_centerFirst;
172 PageViewPrivate::PageViewPrivate( PageView *qq )
173 : q( qq )
177 FormWidgetsController* PageViewPrivate::formWidgetsController()
179 if ( !formsWidgetController )
181 formsWidgetController = new FormWidgetsController();
182 QObject::connect( formsWidgetController, SIGNAL( changed( FormWidgetIface* ) ),
183 q, SLOT( slotFormWidgetChanged( FormWidgetIface * ) ) );
184 QObject::connect( formsWidgetController, SIGNAL( action( Okular::Action* ) ),
185 q, SLOT( slotAction( Okular::Action* ) ) );
188 return formsWidgetController;
191 OkularTTS* PageViewPrivate::tts()
193 if ( !m_tts )
195 m_tts = new OkularTTS( q );
196 if ( aSpeakStop )
198 QObject::connect( m_tts, SIGNAL( hasSpeechs( bool ) ),
199 aSpeakStop, SLOT( setEnabled( bool ) ) );
200 QObject::connect( m_tts, SIGNAL( errorMessage( const QString & ) ),
201 q, SLOT( errorMessage( const QString & ) ) );
205 return m_tts;
209 class PageViewWidget : public QWidget
211 public:
212 PageViewWidget(PageView *pv) : QWidget(pv), m_pageView(pv) {}
214 protected:
215 bool event( QEvent *e )
217 if ( e->type() == QEvent::ToolTip && m_pageView->d->mouseMode == PageView::MouseNormal )
219 QHelpEvent * he = (QHelpEvent*)e;
220 PageViewItem * pageItem = m_pageView->pickItemOnPoint( he->x(), he->y() );
221 const Okular::ObjectRect * rect = 0;
222 const Okular::Action * link = 0;
223 const Okular::Annotation * ann = 0;
224 if ( pageItem )
226 double nX = pageItem->absToPageX( he->x() );
227 double nY = pageItem->absToPageY( he->y() );
228 rect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
229 if ( rect )
230 ann = static_cast< const Okular::AnnotationObjectRect * >( rect )->annotation();
231 else
233 rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
234 if ( rect )
235 link = static_cast< const Okular::Action * >( rect->object() );
239 if ( ann )
241 QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
242 r.translate( pageItem->uncroppedGeometry().topLeft() );
243 QString tip = GuiUtils::prettyToolTip( ann );
244 QToolTip::showText( he->globalPos(), tip, this, r );
246 else if ( link )
248 QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
249 r.translate( pageItem->uncroppedGeometry().topLeft() );
250 QString tip = link->actionTip();
251 if ( !tip.isEmpty() )
252 QToolTip::showText( he->globalPos(), tip, this, r );
254 e->accept();
255 return true;
257 else
258 // do not stop the event
259 return QWidget::event( e );
262 // viewport events
263 void paintEvent( QPaintEvent *e )
265 m_pageView->contentsPaintEvent(e);
268 void mouseMoveEvent( QMouseEvent *e )
270 m_pageView->contentsMouseMoveEvent(e);
273 void mousePressEvent( QMouseEvent *e )
275 m_pageView->contentsMousePressEvent(e);
278 void mouseReleaseEvent( QMouseEvent *e )
280 m_pageView->contentsMouseReleaseEvent(e);
284 private:
285 PageView *m_pageView;
288 /* PageView. What's in this file? -> quick overview.
289 * Code weight (in rows) and meaning:
290 * 160 - constructor and creating actions plus their connected slots (empty stuff)
291 * 70 - DocumentObserver inherited methodes (important)
292 * 550 - events: mouse, keyboard, drag/drop
293 * 170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes
294 * 100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc..
295 * other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable,
296 * and many insignificant stuff like this comment :-)
298 PageView::PageView( QWidget *parent, Okular::Document *document )
299 : QScrollArea( parent )
300 , Okular::View( QString::fromLatin1( "PageView" ) )
302 // create and initialize private storage structure
303 d = new PageViewPrivate( this );
304 d->document = document;
305 d->aRotateClockwise = 0;
306 d->aRotateCounterClockwise = 0;
307 d->aRotateOriginal = 0;
308 d->aViewMode = 0;
309 d->zoomMode = PageView::ZoomFitWidth;
310 d->zoomFactor = 1.0;
311 d->mouseMode = MouseNormal;
312 d->mouseMidZooming = false;
313 d->mouseSelecting = false;
314 d->mouseTextSelecting = false;
315 d->mouseOnRect = false;
316 d->mouseAnn = 0;
317 d->viewportMoveActive = false;
318 d->viewportMoveTimer = 0;
319 d->scrollIncrement = 0;
320 d->autoScrollTimer = 0;
321 d->annotator = 0;
322 d->delayResizeTimer = 0;
323 d->dirtyLayout = false;
324 d->blockViewport = false;
325 d->blockPixmapsRequest = false;
326 d->messageWindow = new PageViewMessage(this);
327 d->m_formsVisible = false;
328 d->formsWidgetController = 0;
329 d->m_tts = 0;
330 d->refreshTimer = 0;
331 d->refreshPage = -1;
332 d->aRotateClockwise = 0;
333 d->aRotateCounterClockwise = 0;
334 d->aRotateOriginal = 0;
335 d->aPageSizes = 0;
336 d->aTrimMargins = 0;
337 d->aMouseNormal = 0;
338 d->aMouseSelect = 0;
339 d->aMouseTextSelect = 0;
340 d->aToggleAnnotator = 0;
341 d->aZoomFitWidth = 0;
342 d->aZoomFitPage = 0;
343 d->aZoomFitText = 0;
344 d->aViewMode = 0;
345 d->aViewContinuous = 0;
346 d->aPrevAction = 0;
347 d->aToggleForms = 0;
348 d->aSpeakDoc = 0;
349 d->aSpeakPage = 0;
350 d->aSpeakStop = 0;
351 d->actionCollection = 0;
352 d->aPageSizes=0;
353 d->setting_viewMode = Okular::Settings::viewMode();
354 d->setting_viewCols = Okular::Settings::viewColumns();
355 d->setting_centerFirst = Okular::Settings::centerFirstPageInRow();
357 setFrameStyle(QFrame::NoFrame);
359 setAttribute( Qt::WA_StaticContents );
361 setObjectName( QLatin1String( "okular::pageView" ) );
363 // widget setup: setup focus, accept drops and track mouse
364 setWidget(new PageViewWidget(this));
365 viewport()->setFocusProxy( this );
366 viewport()->setFocusPolicy( Qt::StrongFocus );
367 widget()->setAttribute( Qt::WA_OpaquePaintEvent );
368 widget()->setAttribute( Qt::WA_NoSystemBackground );
369 setAcceptDrops( true );
370 widget()->setMouseTracking( true );
371 setWidgetResizable(true);
373 // conntect the padding of the viewport to pixmaps requests
374 connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRequestVisiblePixmaps(int)));
375 connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRequestVisiblePixmaps(int)));
376 connect( &d->dragScrollTimer, SIGNAL(timeout()), this, SLOT(slotDragScroll()) );
378 // set a corner button to resize the view to the page size
379 // QPushButton * resizeButton = new QPushButton( viewport() );
380 // resizeButton->setPixmap( SmallIcon("crop") );
381 // setCornerWidget( resizeButton );
382 // resizeButton->setEnabled( false );
383 // connect(...);
384 setAttribute( Qt::WA_InputMethodEnabled, true );
386 // schedule the welcome message
387 QMetaObject::invokeMethod(this, "slotShowWelcome", Qt::QueuedConnection);
390 PageView::~PageView()
392 if ( d->m_tts )
393 d->m_tts->stopAllSpeechs();
395 // delete the local storage structure
396 qDeleteAll(d->m_annowindows);
397 // delete all widgets
398 QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd();
399 for ( ; dIt != dEnd; ++dIt )
400 delete *dIt;
401 delete d->formsWidgetController;
402 d->document->removeObserver( this );
403 delete d;
406 void PageView::setupBaseActions( KActionCollection * ac )
408 d->actionCollection = ac;
410 // Zoom actions ( higher scales takes lots of memory! )
411 d->aZoom = new KSelectAction(KIcon( "zoom-original" ), i18n("Zoom"), this);
412 ac->addAction("zoom_to", d->aZoom );
413 d->aZoom->setEditable( true );
414 d->aZoom->setMaxComboViewCount( 13 );
415 connect( d->aZoom, SIGNAL( triggered(QAction *) ), this, SLOT( slotZoom() ) );
416 updateZoomText();
418 d->aZoomIn = KStandardAction::zoomIn( this, SLOT( slotZoomIn() ), ac );
420 d->aZoomOut = KStandardAction::zoomOut( this, SLOT( slotZoomOut() ), ac );
423 void PageView::setupActions( KActionCollection * ac )
425 d->actionCollection = ac;
427 // orientation menu actions
428 d->aRotateClockwise = new KAction( KIcon( "object-rotate-right" ), i18n( "Rotate Right" ), this );
429 d->aRotateClockwise->setIconText( i18nc( "Rotate right", "Right" ) );
430 ac->addAction( "view_orientation_rotate_cw", d->aRotateClockwise );
431 d->aRotateClockwise->setEnabled( false );
432 connect( d->aRotateClockwise, SIGNAL( triggered() ), this, SLOT( slotRotateClockwise() ) );
433 d->aRotateCounterClockwise = new KAction( KIcon( "object-rotate-left" ), i18n( "Rotate Left" ), this );
434 d->aRotateCounterClockwise->setIconText( i18nc( "Rotate left", "Left" ) );
435 ac->addAction( "view_orientation_rotate_ccw", d->aRotateCounterClockwise );
436 d->aRotateCounterClockwise->setEnabled( false );
437 connect( d->aRotateCounterClockwise, SIGNAL( triggered() ), this, SLOT( slotRotateCounterClockwise() ) );
438 d->aRotateOriginal = new KAction( i18n( "Original Orientation" ), this );
439 ac->addAction( "view_orientation_original", d->aRotateOriginal );
440 d->aRotateOriginal->setEnabled( false );
441 connect( d->aRotateOriginal, SIGNAL( triggered() ), this, SLOT( slotRotateOriginal() ) );
443 d->aPageSizes = new KSelectAction(i18n("&Page Size"), this);
444 ac->addAction("view_pagesizes", d->aPageSizes);
445 d->aPageSizes->setEnabled( false );
447 connect( d->aPageSizes , SIGNAL( triggered( int ) ),
448 this, SLOT( slotPageSizes( int ) ) );
450 d->aTrimMargins = new KToggleAction( i18n( "&Trim Margins" ), this );
451 ac->addAction("view_trim_margins", d->aTrimMargins );
452 connect( d->aTrimMargins, SIGNAL( toggled( bool ) ), SLOT( slotTrimMarginsToggled( bool ) ) );
453 d->aTrimMargins->setChecked( Okular::Settings::trimMargins() );
455 d->aZoomFitWidth = new KToggleAction(KIcon( "zoom-fit-width" ), i18n("Fit &Width"), this);
456 ac->addAction("view_fit_to_width", d->aZoomFitWidth );
457 connect( d->aZoomFitWidth, SIGNAL( toggled( bool ) ), SLOT( slotFitToWidthToggled( bool ) ) );
459 d->aZoomFitPage = new KToggleAction(KIcon( "zoom-fit-best" ), i18n("Fit &Page"), this);
460 ac->addAction("view_fit_to_page", d->aZoomFitPage );
461 connect( d->aZoomFitPage, SIGNAL( toggled( bool ) ), SLOT( slotFitToPageToggled( bool ) ) );
464 d->aZoomFitText = new KToggleAction(KIcon( "zoom-fit-best" ), i18n("Fit &Text"), this);
465 ac->addAction("zoom_fit_text", d->aZoomFitText );
466 connect( d->aZoomFitText, SIGNAL( toggled( bool ) ), SLOT( slotFitToTextToggled( bool ) ) );
469 // View-Layout actions
470 d->aViewMode = new KActionMenu( KIcon( "view-split-left-right" ), i18n( "&View Mode" ), this );
471 d->aViewMode->setDelayed( false );
472 #define ADD_VIEWMODE_ACTION( text, name, id ) \
473 do { \
474 KAction *vm = new KAction( text, d->aViewMode->menu() ); \
475 vm->setCheckable( true ); \
476 vm->setData( qVariantFromValue( id ) ); \
477 d->aViewMode->addAction( vm ); \
478 ac->addAction( name, vm ); \
479 vmGroup->addAction( vm ); \
480 } while( 0 )
481 ac->addAction("view_render_mode", d->aViewMode );
482 QActionGroup *vmGroup = new QActionGroup( d->aViewMode->menu() );
483 ADD_VIEWMODE_ACTION( i18n( "Single Page" ), "view_render_mode_single", 0 );
484 ADD_VIEWMODE_ACTION( i18n( "Facing Pages" ), "view_render_mode_facing", 1 );
485 ADD_VIEWMODE_ACTION( i18n( "Overview" ), "view_render_mode_overview", 2 );
486 d->aViewMode->menu()->actions().at( Okular::Settings::viewMode() )->setChecked( true );
487 connect( vmGroup, SIGNAL( triggered( QAction* ) ), this, SLOT( slotViewMode( QAction* ) ) );
488 #undef ADD_VIEWMODE_ACTION
490 d->aViewContinuous = new KToggleAction(KIcon( "view-list-text" ), i18n("&Continuous"), this);
491 ac->addAction("view_continuous", d->aViewContinuous );
492 connect( d->aViewContinuous, SIGNAL( toggled( bool ) ), SLOT( slotContinuousToggled( bool ) ) );
493 d->aViewContinuous->setChecked( Okular::Settings::viewContinuous() );
495 // Mouse-Mode actions
496 QActionGroup * actGroup = new QActionGroup( this );
497 actGroup->setExclusive( true );
498 d->aMouseNormal = new KAction( KIcon( "input-mouse" ), i18n( "&Browse Tool" ), this );
499 ac->addAction("mouse_drag", d->aMouseNormal );
500 connect( d->aMouseNormal, SIGNAL( triggered() ), this, SLOT( slotSetMouseNormal() ) );
501 d->aMouseNormal->setIconText( i18nc( "Browse Tool", "Browse" ) );
502 d->aMouseNormal->setCheckable( true );
503 d->aMouseNormal->setShortcut( Qt::CTRL + Qt::Key_1 );
504 d->aMouseNormal->setActionGroup( actGroup );
505 d->aMouseNormal->setChecked( true );
507 KAction * mz = new KAction(KIcon( "zoom-original" ), i18n("&Zoom Tool"), this);
508 ac->addAction("mouse_zoom", mz );
509 connect( mz, SIGNAL( triggered() ), this, SLOT( slotSetMouseZoom() ) );
510 mz->setIconText( i18nc( "Zoom Tool", "Zoom" ) );
511 mz->setCheckable( true );
512 mz->setShortcut( Qt::CTRL + Qt::Key_2 );
513 mz->setActionGroup( actGroup );
515 d->aMouseSelect = new KAction(KIcon( "select-rectangular" ), i18n("&Selection Tool"), this);
516 ac->addAction("mouse_select", d->aMouseSelect );
517 connect( d->aMouseSelect, SIGNAL( triggered() ), this, SLOT( slotSetMouseSelect() ) );
518 d->aMouseSelect->setIconText( i18nc( "Select Tool", "Selection" ) );
519 d->aMouseSelect->setCheckable( true );
520 d->aMouseSelect->setShortcut( Qt::CTRL + Qt::Key_3 );
521 d->aMouseSelect->setActionGroup( actGroup );
523 d->aMouseTextSelect = new KAction(KIcon( "draw-text" ), i18n("&Text Selection Tool"), this);
524 ac->addAction("mouse_textselect", d->aMouseTextSelect );
525 connect( d->aMouseTextSelect, SIGNAL( triggered() ), this, SLOT( slotSetMouseTextSelect() ) );
526 d->aMouseTextSelect->setIconText( i18nc( "Text Selection Tool", "Text Selection" ) );
527 d->aMouseTextSelect->setCheckable( true );
528 d->aMouseTextSelect->setShortcut( Qt::CTRL + Qt::Key_4 );
529 d->aMouseTextSelect->setActionGroup( actGroup );
531 d->aToggleAnnotator = new KToggleAction(KIcon( "draw-freehand" ), i18n("&Review"), this);
532 ac->addAction("mouse_toggle_annotate", d->aToggleAnnotator );
533 d->aToggleAnnotator->setCheckable( true );
534 connect( d->aToggleAnnotator, SIGNAL( toggled( bool ) ), SLOT( slotToggleAnnotator( bool ) ) );
535 d->aToggleAnnotator->setShortcut( Qt::Key_F6 );
537 ToolAction *ta = new ToolAction( this );
538 ac->addAction( "mouse_selecttools", ta );
539 ta->addAction( d->aMouseSelect );
540 ta->addAction( d->aMouseTextSelect );
542 // speak actions
543 d->aSpeakDoc = new KAction( KIcon( "text-speak" ), i18n( "Speak Whole Document" ), this );
544 ac->addAction( "speak_document", d->aSpeakDoc );
545 d->aSpeakDoc->setEnabled( false );
546 connect( d->aSpeakDoc, SIGNAL( triggered() ), SLOT( slotSpeakDocument() ) );
548 d->aSpeakPage = new KAction( KIcon( "text-speak" ), i18n( "Speak Current Page" ), this );
549 ac->addAction( "speak_current_page", d->aSpeakPage );
550 d->aSpeakPage->setEnabled( false );
551 connect( d->aSpeakPage, SIGNAL( triggered() ), SLOT( slotSpeakCurrentPage() ) );
553 d->aSpeakStop = new KAction( KIcon( "media-playback-stop" ), i18n( "Stop Speaking" ), this );
554 ac->addAction( "speak_stop_all", d->aSpeakStop );
555 d->aSpeakStop->setEnabled( false );
556 connect( d->aSpeakStop, SIGNAL( triggered() ), SLOT( slotStopSpeaks() ) );
558 // Other actions
559 KAction * su = new KAction(i18n("Scroll Up"), this);
560 ac->addAction("view_scroll_up", su );
561 connect( su, SIGNAL( triggered() ), this, SLOT( slotScrollUp() ) );
562 su->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_Up) );
563 addAction(su);
565 KAction * sd = new KAction(i18n("Scroll Down"), this);
566 ac->addAction("view_scroll_down", sd );
567 connect( sd, SIGNAL( triggered() ), this, SLOT( slotScrollDown() ) );
568 sd->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_Down) );
569 addAction(sd);
571 d->aToggleForms = new KAction( this );
572 ac->addAction( "view_toggle_forms", d->aToggleForms );
573 connect( d->aToggleForms, SIGNAL( triggered() ), this, SLOT( slotToggleForms() ) );
574 d->aToggleForms->setEnabled( false );
575 toggleFormWidgets( false );
578 bool PageView::canFitPageWidth() const
580 return Okular::Settings::viewMode() != 0 || d->zoomMode != ZoomFitWidth;
583 void PageView::fitPageWidth( int page )
585 // zoom: Fit Width, columns: 1. setActions + relayout + setPage + update
586 d->zoomMode = ZoomFitWidth;
587 Okular::Settings::setViewMode( 0 );
588 d->aZoomFitWidth->setChecked( true );
589 d->aZoomFitPage->setChecked( false );
590 // d->aZoomFitText->setChecked( false );
591 d->aViewMode->menu()->actions().at( 0 )->setChecked( true );
592 viewport()->setUpdatesEnabled( false );
593 slotRelayoutPages();
594 viewport()->setUpdatesEnabled( true );
595 d->document->setViewportPage( page );
596 updateZoomText();
597 setFocus();
600 void PageView::setAnnotationWindow( Okular::Annotation * annotation )
602 if ( !annotation )
603 return;
605 // find the annot window
606 AnnotWindow* existWindow = 0;
607 QHash< Okular::Annotation *, AnnotWindow * >::ConstIterator it = d->m_annowindows.constFind( annotation );
608 if ( it != d->m_annowindows.constEnd() )
610 existWindow = *it;
613 if ( existWindow == 0 )
615 existWindow = new AnnotWindow( this, annotation );
617 d->m_annowindows.insert( annotation, existWindow );
620 existWindow->show();
623 void PageView::removeAnnotationWindow( Okular::Annotation *annotation )
625 QHash< Okular::Annotation *, AnnotWindow * >::Iterator it = d->m_annowindows.find( annotation );
626 if ( it != d->m_annowindows.end() )
628 delete *it;
629 d->m_annowindows.erase( it );
633 void PageView::displayMessage( const QString & message,PageViewMessage::Icon icon,int duration )
635 if ( !Okular::Settings::showOSD() )
637 if (icon == PageViewMessage::Error)
638 KMessageBox::error( this, message );
639 else
640 return;
643 // hide messageWindow if string is empty
644 if ( message.isEmpty() )
645 return d->messageWindow->hide();
647 // display message (duration is length dependant)
648 if (duration==-1)
649 duration = 500 + 100 * message.length();
650 d->messageWindow->display( message, icon, duration );
653 void PageView::reparseConfig()
655 // set the scroll bars policies
656 Qt::ScrollBarPolicy scrollBarMode = Okular::Settings::showScrollBars() ?
657 Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff;
658 if ( horizontalScrollBarPolicy() != scrollBarMode )
660 setHorizontalScrollBarPolicy( scrollBarMode );
661 setVerticalScrollBarPolicy( scrollBarMode );
664 const int viewMode = Okular::Settings::viewMode();
665 if ( ( viewMode == 2 && ( (int)Okular::Settings::viewColumns() != d->setting_viewCols ) )
666 || ( viewMode > 0 && ( Okular::Settings::centerFirstPageInRow() != d->setting_centerFirst ) )
669 d->setting_viewMode = Okular::Settings::viewMode();
670 d->setting_viewCols = Okular::Settings::viewColumns();
671 d->setting_centerFirst = Okular::Settings::centerFirstPageInRow();
673 slotRelayoutPages();
677 KAction *PageView::toggleFormsAction() const
679 return d->aToggleForms;
682 QString PageViewPrivate::selectedText() const
684 if ( pagesWithTextSelection.isEmpty() )
685 return QString();
687 QString text;
688 QList< int > selpages = pagesWithTextSelection.toList();
689 qSort( selpages );
690 const Okular::Page * pg = 0;
691 if ( selpages.count() == 1 )
693 pg = document->page( selpages.first() );
694 text.append( pg->text( pg->textSelection() ) );
696 else
698 pg = document->page( selpages.first() );
699 text.append( pg->text( pg->textSelection() ) );
700 int end = selpages.count() - 1;
701 for( int i = 1; i < end; ++i )
703 pg = document->page( selpages.at( i ) );
704 text.append( pg->text() );
706 pg = document->page( selpages.last() );
707 text.append( pg->text( pg->textSelection() ) );
709 return text;
712 void PageView::copyTextSelection() const
714 const QString text = d->selectedText();
715 if ( !text.isEmpty() )
717 QClipboard *cb = QApplication::clipboard();
718 cb->setText( text, QClipboard::Clipboard );
722 void PageView::selectAll()
724 if ( d->mouseMode != MouseTextSelect )
725 return;
727 QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd();
728 for ( ; it < itEnd; ++it )
730 Okular::RegularAreaRect * area = textSelectionForItem( *it );
731 d->pagesWithTextSelection.insert( (*it)->pageNumber() );
732 d->document->setPageTextSelection( (*it)->pageNumber(), area, palette().color( QPalette::Active, QPalette::Highlight ) );
736 //BEGIN DocumentObserver inherited methods
737 void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags )
739 bool documentChanged = setupFlags & Okular::DocumentObserver::DocumentChanged;
740 // reuse current pages if nothing new
741 if ( ( pageSet.count() == d->items.count() ) && !documentChanged && !( setupFlags & Okular::DocumentObserver::NewLayoutForPages ) )
743 int count = pageSet.count();
744 for ( int i = 0; (i < count) && !documentChanged; i++ )
745 if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() )
746 documentChanged = true;
747 if ( !documentChanged )
748 return;
751 // delete all widgets (one for each page in pageSet)
752 QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd();
753 for ( ; dIt != dEnd; ++dIt )
754 delete *dIt;
755 d->items.clear();
756 d->visibleItems.clear();
757 d->pagesWithTextSelection.clear();
758 toggleFormWidgets( false );
759 if ( d->formsWidgetController )
760 d->formsWidgetController->dropRadioButtons();
762 bool haspages = !pageSet.isEmpty();
763 bool hasformwidgets = false;
764 // create children widgets
765 QVector< Okular::Page * >::const_iterator setIt = pageSet.constBegin(), setEnd = pageSet.constEnd();
766 for ( ; setIt != setEnd; ++setIt )
768 PageViewItem * item = new PageViewItem( *setIt );
769 d->items.push_back( item );
770 #ifdef PAGEVIEW_DEBUG
771 kDebug().nospace() << "cropped geom for " << d->items.last()->pageNumber() << " is " << d->items.last()->croppedGeometry();
772 #endif
773 const QLinkedList< Okular::FormField * > pageFields = (*setIt)->formFields();
774 QLinkedList< Okular::FormField * >::const_iterator ffIt = pageFields.constBegin(), ffEnd = pageFields.constEnd();
775 for ( ; ffIt != ffEnd; ++ffIt )
777 Okular::FormField * ff = *ffIt;
778 FormWidgetIface * w = FormWidgetFactory::createWidget( ff, widget() );
779 if ( w )
781 w->setPageItem( item );
782 w->setFormWidgetsController( d->formWidgetsController() );
783 w->setVisibility( false );
784 w->setCanBeFilled( d->document->isAllowed( Okular::AllowFillForms ) );
785 item->formWidgets().insert( ff->id(), w );
786 hasformwidgets = true;
789 const QLinkedList< Okular::Annotation * > annotations = (*setIt)->annotations();
790 QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.constBegin(), aEnd = annotations.constEnd();
791 for ( ; aIt != aEnd; ++aIt )
793 Okular::Annotation * a = *aIt;
794 if ( a->subType() == Okular::Annotation::AMovie )
796 Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a );
797 VideoWidget * vw = new VideoWidget( movieAnn, d->document, widget() );
798 item->videoWidgets().insert( movieAnn->movie(), vw );
799 vw->show();
804 // invalidate layout so relayout/repaint will happen on next viewport change
805 if ( haspages )
806 // TODO for Enrico: Check if doing always the slotRelayoutPages() is not
807 // suboptimal in some cases, i'd say it is not but a recheck will not hurt
808 // Need slotRelayoutPages() here instead of d->dirtyLayout = true
809 // because opening a document from another document will not trigger a viewportchange
810 // so pages are never relayouted
811 QMetaObject::invokeMethod(this, "slotRelayoutPages", Qt::QueuedConnection);
812 else
814 // update the mouse cursor when closing because we may have close through a link and
815 // want the cursor to come back to the normal cursor
816 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
817 setWidgetResizable(true);
820 // OSD to display pages
821 if ( documentChanged && pageSet.count() > 0 && Okular::Settings::showOSD() )
822 d->messageWindow->display(
823 i18np(" Loaded a one-page document.",
824 " Loaded a %1-page document.",
825 pageSet.count() ),
826 PageViewMessage::Info, 4000 );
828 if ( d->aPageSizes )
829 { // may be null if dummy mode is on
830 bool pageSizes = d->document->supportsPageSizes();
831 d->aPageSizes->setEnabled( pageSizes );
832 // set the new page sizes:
833 // - if the generator supports them
834 // - if the document changed
835 if ( pageSizes && documentChanged )
837 QStringList items;
838 foreach ( const Okular::PageSize &p, d->document->pageSizes() )
839 items.append( p.name() );
840 d->aPageSizes->setItems( items );
843 if ( d->aRotateClockwise )
844 d->aRotateClockwise->setEnabled( haspages );
845 if ( d->aRotateCounterClockwise )
846 d->aRotateCounterClockwise->setEnabled( haspages );
847 if ( d->aRotateOriginal )
848 d->aRotateOriginal->setEnabled( haspages );
849 if ( d->aToggleForms )
850 { // may be null if dummy mode is on
851 d->aToggleForms->setEnabled( haspages && hasformwidgets );
853 bool allowAnnotations = d->document->isAllowed( Okular::AllowNotes );
854 if ( d->annotator )
856 bool allowTools = haspages && allowAnnotations;
857 d->annotator->setToolsEnabled( allowTools );
858 d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() );
860 if ( d->aToggleAnnotator )
862 if ( !allowAnnotations && d->aToggleAnnotator->isChecked() )
864 d->aToggleAnnotator->trigger();
866 d->aToggleAnnotator->setEnabled( allowAnnotations );
868 if ( d->aSpeakDoc )
870 const bool enablettsactions = haspages ? Okular::Settings::useKTTSD() : false;
871 d->aSpeakDoc->setEnabled( enablettsactions );
872 d->aSpeakPage->setEnabled( enablettsactions );
876 void PageView::notifyViewportChanged( bool smoothMove )
878 // if we are the one changing viewport, skip this nofity
879 if ( d->blockViewport )
880 return;
882 // block setViewport outgoing calls
883 d->blockViewport = true;
885 // find PageViewItem matching the viewport description
886 const Okular::DocumentViewport & vp = d->document->viewport();
887 PageViewItem * item = 0;
888 QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd();
889 for ( ; iIt != iEnd; ++iIt )
890 if ( (*iIt)->pageNumber() == vp.pageNumber )
892 item = *iIt;
893 break;
895 if ( !item )
897 kWarning() << "viewport for page" << vp.pageNumber << "has no matching item!";
898 d->blockViewport = false;
899 return;
901 #ifdef PAGEVIEW_DEBUG
902 kDebug() << "document viewport changed";
903 #endif
904 // relayout in "Single Pages" mode or if a relayout is pending
905 d->blockPixmapsRequest = true;
906 if ( !Okular::Settings::viewContinuous() || d->dirtyLayout )
907 slotRelayoutPages();
909 // restore viewport center or use default {x-center,v-top} alignment
910 const QRect & r = item->croppedGeometry();
911 int newCenterX = r.left(),
912 newCenterY = r.top();
913 if ( vp.rePos.enabled )
915 if ( vp.rePos.pos == Okular::DocumentViewport::Center )
917 newCenterX += (int)( vp.rePos.normalizedX * (double)r.width() );
918 newCenterY += (int)( vp.rePos.normalizedY * (double)r.height() );
920 else
922 // TopLeft
923 newCenterX += (int)( vp.rePos.normalizedX * (double)r.width() + viewport()->width() / 2 );
924 newCenterY += (int)( vp.rePos.normalizedY * (double)r.height() + viewport()->height() / 2 );
927 else
929 newCenterX += r.width() / 2;
930 newCenterY += viewport()->height() / 2 - 10;
933 // if smooth movement requested, setup parameters and start it
934 if ( smoothMove )
936 d->viewportMoveActive = true;
937 d->viewportMoveTime.start();
938 d->viewportMoveDest.setX( newCenterX );
939 d->viewportMoveDest.setY( newCenterY );
940 if ( !d->viewportMoveTimer )
942 d->viewportMoveTimer = new QTimer( this );
943 connect( d->viewportMoveTimer, SIGNAL( timeout() ),
944 this, SLOT( slotMoveViewport() ) );
946 d->viewportMoveTimer->start( 25 );
947 verticalScrollBar()->setEnabled( false );
948 horizontalScrollBar()->setEnabled( false );
950 else
951 center( newCenterX, newCenterY );
952 d->blockPixmapsRequest = false;
954 // request visible pixmaps in the current viewport and recompute it
955 slotRequestVisiblePixmaps();
957 // enable setViewport calls
958 d->blockViewport = false;
960 // update zoom text if in a ZoomFit/* zoom mode
961 if ( d->zoomMode != ZoomFixed )
962 updateZoomText();
964 // since the page has moved below cursor, update it
965 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
968 void PageView::notifyPageChanged( int pageNumber, int changedFlags )
970 // only handle pixmap / highlight changes notifies
971 if ( changedFlags & DocumentObserver::Bookmark )
972 return;
974 if ( changedFlags & DocumentObserver::Annotations )
976 const QLinkedList< Okular::Annotation * > annots = d->document->page( pageNumber )->annotations();
977 const QLinkedList< Okular::Annotation * >::ConstIterator annItEnd = annots.end();
978 QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin();
979 for ( ; it != d->m_annowindows.end(); )
981 QLinkedList< Okular::Annotation * >::ConstIterator annIt = qFind( annots, it.key() );
982 if ( annIt != annItEnd )
984 (*it)->reloadInfo();
985 ++it;
987 else
989 delete *it;
990 it = d->m_annowindows.erase( it );
995 if ( changedFlags & DocumentObserver::BoundingBox )
997 #ifdef PAGEVIEW_DEBUG
998 kDebug() << "BoundingBox change on page" << pageNumber;
999 #endif
1000 slotRelayoutPages();
1001 slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already!
1002 // Repaint the whole widget since layout may have changed
1003 widget()->update();
1004 return;
1007 // iterate over visible items: if page(pageNumber) is one of them, repaint it
1008 QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd();
1009 for ( ; iIt != iEnd; ++iIt )
1010 if ( (*iIt)->pageNumber() == pageNumber && (*iIt)->isVisible() )
1012 // update item's rectangle plus the little outline
1013 QRect expandedRect = (*iIt)->croppedGeometry();
1014 expandedRect.adjust( -1, -1, 3, 3 );
1015 widget()->update( expandedRect );
1017 // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor
1018 if ( cursor().shape() != Qt::SizeVerCursor )
1020 // since the page has been regenerated below cursor, update it
1021 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
1023 break;
1027 void PageView::notifyContentsCleared( int changedFlags )
1029 // if pixmaps were cleared, re-ask them
1030 if ( changedFlags & DocumentObserver::Pixmap )
1031 QMetaObject::invokeMethod(this, "slotRequestVisiblePixmaps", Qt::QueuedConnection);
1034 void PageView::notifyZoom( int factor )
1036 if ( factor > 0 )
1037 updateZoom( ZoomIn );
1038 else
1039 updateZoom( ZoomOut );
1042 bool PageView::canUnloadPixmap( int pageNumber ) const
1044 if ( Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Aggressive )
1046 // if the item is visible, forbid unloading
1047 QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd();
1048 for ( ; vIt != vEnd; ++vIt )
1049 if ( (*vIt)->pageNumber() == pageNumber )
1050 return false;
1052 else
1054 // forbid unloading of the visible items, and of the previous and next
1055 QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd();
1056 for ( ; vIt != vEnd; ++vIt )
1057 if ( abs( (*vIt)->pageNumber() - pageNumber ) <= 1 )
1058 return false;
1060 // if hidden premit unloading
1061 return true;
1063 //END DocumentObserver inherited methods
1065 //BEGIN View inherited methods
1066 bool PageView::supportsCapability( ViewCapability capability ) const
1068 switch ( capability )
1070 case Zoom:
1071 case ZoomModality:
1072 return true;
1074 return false;
1077 Okular::View::CapabilityFlags PageView::capabilityFlags( ViewCapability capability ) const
1079 switch ( capability )
1081 case Zoom:
1082 case ZoomModality:
1083 return CapabilityRead | CapabilityWrite | CapabilitySerializable;
1085 return 0;
1088 QVariant PageView::capability( ViewCapability capability ) const
1090 switch ( capability )
1092 case Zoom:
1093 return d->zoomFactor;
1094 case ZoomModality:
1095 return d->zoomMode;
1097 return QVariant();
1100 void PageView::setCapability( ViewCapability capability, const QVariant &option )
1102 switch ( capability )
1104 case Zoom:
1106 bool ok = true;
1107 double factor = option.toDouble( &ok );
1108 if ( ok && factor > 0.0 )
1110 d->zoomFactor = static_cast< float >( factor );
1111 updateZoom( ZoomRefreshCurrent );
1113 break;
1115 case ZoomModality:
1117 bool ok = true;
1118 int mode = option.toInt( &ok );
1119 if ( ok )
1121 if ( mode >= 0 && mode < 3 )
1122 updateZoom( (ZoomMode)mode );
1124 break;
1129 //END View inherited methods
1131 //BEGIN widget events
1132 void PageView::contentsPaintEvent(QPaintEvent *pe)
1134 // create the rect into contents from the clipped screen rect
1135 QRect viewportRect = viewport()->rect();
1136 viewportRect.translate( horizontalScrollBar()->value(), verticalScrollBar()->value() );
1137 QRect contentsRect = pe->rect().intersect( viewportRect );
1138 if ( !contentsRect.isValid() )
1139 return;
1141 #ifdef PAGEVIEW_DEBUG
1142 kDebug() << "paintevent" << contentsRect;
1143 #endif
1145 // create the screen painter. a pixel painted at contentsX,contentsY
1146 // appears to the top-left corner of the scrollview.
1147 QPainter screenPainter( widget() );
1149 // selectionRect is the normalized mouse selection rect
1150 QRect selectionRect = d->mouseSelectionRect;
1151 if ( !selectionRect.isNull() )
1152 selectionRect = selectionRect.normalized();
1153 // selectionRectInternal without the border
1154 QRect selectionRectInternal = selectionRect;
1155 selectionRectInternal.adjust( 1, 1, -1, -1 );
1156 // color for blending
1157 QColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ?
1158 d->mouseSelectionColor : Qt::red;
1160 // subdivide region into rects
1161 const QVector<QRect> &allRects = pe->region().rects();
1162 uint numRects = allRects.count();
1164 // preprocess rects area to see if it worths or not using subdivision
1165 uint summedArea = 0;
1166 for ( uint i = 0; i < numRects; i++ )
1168 const QRect & r = allRects[i];
1169 summedArea += r.width() * r.height();
1171 // very elementary check: SUMj(Region[j].area) is less than boundingRect.area
1172 bool useSubdivision = summedArea < (0.6 * contentsRect.width() * contentsRect.height());
1173 if ( !useSubdivision )
1174 numRects = 1;
1176 // iterate over the rects (only one loop if not using subdivision)
1177 for ( uint i = 0; i < numRects; i++ )
1179 if ( useSubdivision )
1181 // set 'contentsRect' to a part of the sub-divided region
1182 contentsRect = allRects[i].normalized().intersect( viewportRect );
1183 if ( !contentsRect.isValid() )
1184 continue;
1186 #ifdef PAGEVIEW_DEBUG
1187 kDebug() << contentsRect;
1188 #endif
1190 // note: this check will take care of all things requiring alpha blending (not only selection)
1191 bool wantCompositing = !selectionRect.isNull() && contentsRect.intersects( selectionRect );
1193 if ( wantCompositing && Okular::Settings::enableCompositing() )
1195 // create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0})
1196 QPixmap doubleBuffer( contentsRect.size() );
1197 QPainter pixmapPainter( &doubleBuffer );
1198 pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() );
1200 // 1) Layer 0: paint items and clear bg on unpainted rects
1201 drawDocumentOnPainter( contentsRect, &pixmapPainter );
1202 // 2) Layer 1a: paint (blend) transparent selection
1203 if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
1204 !selectionRectInternal.contains( contentsRect ) )
1206 QRect blendRect = selectionRectInternal.intersect( contentsRect );
1207 // skip rectangles covered by the selection's border
1208 if ( blendRect.isValid() )
1210 // grab current pixmap into a new one to colorize contents
1211 QPixmap blendedPixmap( blendRect.width(), blendRect.height() );
1212 QPainter p( &blendedPixmap );
1213 p.drawPixmap( 0, 0, doubleBuffer,
1214 blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(),
1215 blendRect.width(), blendRect.height() );
1217 QColor blCol = selBlendColor.dark( 140 );
1218 blCol.setAlphaF( 0.2 );
1219 p.fillRect( blendedPixmap.rect(), blCol );
1220 p.end();
1221 // copy the blended pixmap back to its place
1222 pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap );
1224 // draw border (red if the selection is too small)
1225 pixmapPainter.setPen( selBlendColor );
1226 pixmapPainter.drawRect( selectionRect.adjusted( 0, 0, -1, -1 ) );
1228 // 3) Layer 1: give annotator painting control
1229 if ( d->annotator && d->annotator->routePaints( contentsRect ) )
1230 d->annotator->routePaint( &pixmapPainter, contentsRect );
1231 // 4) Layer 2: overlays
1232 if ( Okular::Settings::debugDrawBoundaries() )
1234 pixmapPainter.setPen( Qt::blue );
1235 pixmapPainter.drawRect( contentsRect );
1238 // finish painting and draw contents
1239 pixmapPainter.end();
1240 screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer );
1242 else
1244 // 1) Layer 0: paint items and clear bg on unpainted rects
1245 drawDocumentOnPainter( contentsRect, &screenPainter );
1246 // 2) Layer 1: paint opaque selection
1247 if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
1248 !selectionRectInternal.contains( contentsRect ) )
1250 screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) );
1251 screenPainter.drawRect( selectionRect );
1253 // 3) Layer 1: give annotator painting control
1254 if ( d->annotator && d->annotator->routePaints( contentsRect ) )
1255 d->annotator->routePaint( &screenPainter, contentsRect );
1256 // 4) Layer 2: overlays
1257 if ( Okular::Settings::debugDrawBoundaries() )
1259 screenPainter.setPen( Qt::red );
1260 screenPainter.drawRect( contentsRect );
1266 void PageView::resizeEvent( QResizeEvent *e )
1268 if ( d->items.isEmpty() )
1270 widget()->resize(e->size());
1271 return;
1274 if ( d->zoomMode == ZoomFitWidth && d->bothScrollbarsVisible && !horizontalScrollBar()->isVisible() && !verticalScrollBar()->isVisible() && qAbs(e->oldSize().height() - e->size().height()) < horizontalScrollBar()->height() * 1.25 )
1276 // this saves us from infinite resizing loop because of scrollbars appearing and disappearing
1277 // see bug 160628 for more info
1278 // TODO looks are still a bit ugly because things are left uncentered
1279 // but better a bit ugly than unusable
1280 d->bothScrollbarsVisible = false;
1281 widget()->resize( e->size() );
1282 return;
1285 // start a timer that will refresh the pixmap after 0.2s
1286 if ( !d->delayResizeTimer )
1288 d->delayResizeTimer = new QTimer( this );
1289 d->delayResizeTimer->setSingleShot( true );
1290 connect( d->delayResizeTimer, SIGNAL( timeout() ), this, SLOT( slotRelayoutPages() ) );
1292 d->delayResizeTimer->start( 200 );
1294 d->bothScrollbarsVisible = horizontalScrollBar()->isVisible() && verticalScrollBar()->isVisible();
1297 void PageView::keyPressEvent( QKeyEvent * e )
1299 e->accept();
1301 // if performing a selection or dyn zooming, disable keys handling
1302 if ( ( d->mouseSelecting && e->key() != Qt::Key_Escape ) || d->mouseMidZooming )
1303 return;
1305 // if viewport is moving, disable keys handling
1306 if ( d->viewportMoveActive )
1307 return;
1309 // move/scroll page by using keys
1310 switch ( e->key() )
1312 case Qt::Key_J:
1313 case Qt::Key_K:
1314 case Qt::Key_Down:
1315 case Qt::Key_PageDown:
1316 case Qt::Key_Space:
1317 case Qt::Key_Up:
1318 case Qt::Key_PageUp:
1319 case Qt::Key_Backspace:
1320 if ( e->key() == Qt::Key_Down
1321 || e->key() == Qt::Key_PageDown
1322 || e->key() == Qt::Key_J
1323 || ( e->key() == Qt::Key_Space && ( e->modifiers() & Qt::ShiftModifier ) != Qt::ShiftModifier ) )
1325 // if in single page mode and at the bottom of the screen, go to next page
1326 if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() < verticalScrollBar()->maximum() )
1328 if ( e->key() == Qt::Key_Down || e->key() == Qt::Key_J )
1329 verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd );
1330 else
1331 verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepAdd );
1333 else if ( (int)d->document->currentPage() < d->items.count() - 1 )
1335 // more optimized than document->setNextPage and then move view to top
1336 Okular::DocumentViewport newViewport = d->document->viewport();
1337 newViewport.pageNumber += d->document->currentPage() ? viewColumns() : 1;
1338 if ( newViewport.pageNumber >= (int)d->items.count() )
1339 newViewport.pageNumber = d->items.count() - 1;
1340 newViewport.rePos.enabled = true;
1341 newViewport.rePos.normalizedY = 0.0;
1342 d->document->setViewport( newViewport );
1345 else
1347 // if in single page mode and at the top of the screen, go to \ page
1348 if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() > verticalScrollBar()->minimum() )
1350 if ( e->key() == Qt::Key_Up || e->key() == Qt::Key_K )
1351 verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub );
1352 else
1353 verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepSub );
1355 else if ( d->document->currentPage() > 0 )
1357 // more optimized than document->setPrevPage and then move view to bottom
1358 Okular::DocumentViewport newViewport = d->document->viewport();
1359 newViewport.pageNumber -= viewColumns();
1360 if ( newViewport.pageNumber < 0 )
1361 newViewport.pageNumber = 0;
1362 newViewport.rePos.enabled = true;
1363 newViewport.rePos.normalizedY = 1.0;
1364 d->document->setViewport( newViewport );
1367 break;
1368 case Qt::Key_Left:
1369 case Qt::Key_H:
1370 horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub );
1371 break;
1372 case Qt::Key_Right:
1373 case Qt::Key_L:
1374 horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd );
1375 break;
1376 case Qt::Key_Escape:
1377 selectionClear();
1378 d->mousePressPos = QPoint();
1379 if ( d->aPrevAction )
1381 d->aPrevAction->trigger();
1382 d->aPrevAction = 0;
1384 break;
1385 case Qt::Key_Shift:
1386 case Qt::Key_Control:
1387 if ( d->autoScrollTimer )
1389 if ( d->autoScrollTimer->isActive() )
1390 d->autoScrollTimer->stop();
1391 else
1392 slotAutoScoll();
1393 return;
1395 // else fall trhough
1396 default:
1397 e->ignore();
1398 return;
1400 // if a known key has been pressed, stop scrolling the page
1401 if ( d->autoScrollTimer )
1403 d->scrollIncrement = 0;
1404 d->autoScrollTimer->stop();
1408 void PageView::keyReleaseEvent( QKeyEvent * e )
1410 e->accept();
1412 if ( d->annotator && d->annotator->routeEvents() )
1414 if ( d->annotator->routeKeyEvent( e ) )
1415 return;
1418 if ( e->key() == Qt::Key_Escape && d->autoScrollTimer )
1420 d->scrollIncrement = 0;
1421 d->autoScrollTimer->stop();
1425 void PageView::inputMethodEvent( QInputMethodEvent * e )
1427 Q_UNUSED(e)
1430 static QPoint rotateInRect( const QPoint &rotated, Okular::Rotation rotation )
1432 QPoint ret;
1434 switch ( rotation )
1436 case Okular::Rotation90:
1437 ret = QPoint( rotated.y(), -rotated.x() );
1438 break;
1439 case Okular::Rotation180:
1440 ret = QPoint( -rotated.x(), -rotated.y() );
1441 break;
1442 case Okular::Rotation270:
1443 ret = QPoint( -rotated.y(), rotated.x() );
1444 break;
1445 case Okular::Rotation0: // no modifications
1446 default: // other cases
1447 ret = rotated;
1450 return ret;
1453 void PageView::contentsMouseMoveEvent( QMouseEvent * e )
1455 // don't perform any mouse action when no document is shown
1456 if ( d->items.isEmpty() )
1457 return;
1459 // don't perform any mouse action when viewport is autoscrolling
1460 if ( d->viewportMoveActive )
1461 return;
1463 // if holding mouse mid button, perform zoom
1464 if ( d->mouseMidZooming && (e->buttons() & Qt::MidButton) )
1466 int mouseY = e->globalPos().y();
1467 int deltaY = d->mouseMidLastY - mouseY;
1469 // wrap mouse from top to bottom
1470 QRect mouseContainer = KGlobalSettings::desktopGeometry( this );
1471 if ( mouseY <= mouseContainer.top() + 4 &&
1472 d->zoomFactor < 3.99 )
1474 mouseY = mouseContainer.bottom() - 5;
1475 QCursor::setPos( e->globalPos().x(), mouseY );
1477 // wrap mouse from bottom to top
1478 else if ( mouseY >= mouseContainer.bottom() - 4 &&
1479 d->zoomFactor > 0.11 )
1481 mouseY = mouseContainer.top() + 5;
1482 QCursor::setPos( e->globalPos().x(), mouseY );
1484 // remember last position
1485 d->mouseMidLastY = mouseY;
1487 // update zoom level, perform zoom and redraw
1488 if ( deltaY )
1490 d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) );
1491 updateZoom( ZoomRefreshCurrent );
1492 viewport()->repaint();
1494 return;
1497 // if we're editing an annotation, dispatch event to it
1498 if ( d->annotator && d->annotator->routeEvents() )
1500 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1501 d->annotator->routeEvent( e, pageItem );
1502 return;
1505 bool leftButton = (e->buttons() == Qt::LeftButton);
1506 bool rightButton = (e->buttons() == Qt::RightButton);
1507 switch ( d->mouseMode )
1509 case MouseNormal:
1510 if ( leftButton )
1512 if ( d->mouseAnn )
1514 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1515 if ( pageItem )
1517 const QRect & itemRect = pageItem->uncroppedGeometry();
1518 QPoint newpos = QPoint( e->x(), e->y() ) - itemRect.topLeft();
1519 Okular::NormalizedRect r = d->mouseAnn->boundingRectangle();
1520 QPoint p( newpos - d->mouseAnnPos );
1521 QPointF pf( rotateInRect( p, pageItem->page()->rotation() ) );
1522 if ( pageItem->page()->rotation() % 2 == 0 )
1524 pf.rx() /= pageItem->uncroppedWidth();
1525 pf.ry() /= pageItem->uncroppedHeight();
1527 else
1529 pf.rx() /= pageItem->uncroppedHeight();
1530 pf.ry() /= pageItem->uncroppedWidth();
1532 d->mouseAnn->translate( Okular::NormalizedPoint( pf.x(), pf.y() ) );
1533 d->mouseAnnPos = newpos;
1534 d->document->modifyPageAnnotation( pageItem->pageNumber(), d->mouseAnn );
1537 // drag page
1538 else if ( !d->mouseGrabPos.isNull() )
1540 QPoint mousePos = e->globalPos();
1541 QPoint delta = d->mouseGrabPos - mousePos;
1543 // wrap mouse from top to bottom
1544 QRect mouseContainer = KGlobalSettings::desktopGeometry( this );
1545 if ( mousePos.y() <= mouseContainer.top() + 4 &&
1546 verticalScrollBar()->value() < verticalScrollBar()->maximum() - 10 )
1548 mousePos.setY( mouseContainer.bottom() - 5 );
1549 QCursor::setPos( mousePos );
1551 // wrap mouse from bottom to top
1552 else if ( mousePos.y() >= mouseContainer.bottom() - 4 &&
1553 verticalScrollBar()->value() > 10 )
1555 mousePos.setY( mouseContainer.top() + 5 );
1556 QCursor::setPos( mousePos );
1558 // remember last position
1559 d->mouseGrabPos = mousePos;
1561 // scroll page by position increment
1562 horizontalScrollBar()->setValue(horizontalScrollBar()->value() + delta.x());
1563 verticalScrollBar()->setValue(verticalScrollBar()->value() + delta.y());
1566 else if ( rightButton && !d->mousePressPos.isNull() )
1568 // if mouse moves 5 px away from the press point, switch to 'selection'
1569 int deltaX = d->mousePressPos.x() - e->globalPos().x(),
1570 deltaY = d->mousePressPos.y() - e->globalPos().y();
1571 if ( deltaX > 5 || deltaX < -5 || deltaY > 5 || deltaY < -5 )
1573 d->aPrevAction = d->aMouseNormal;
1574 d->aMouseSelect->trigger();
1575 QPoint newPos(e->x() + deltaX, e->y() + deltaY);
1576 selectionStart( newPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false );
1577 selectionEndPoint( e->pos() );
1578 break;
1581 else
1583 // only hovering the page, so update the cursor
1584 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
1586 break;
1588 case MouseZoom:
1589 case MouseSelect:
1590 case MouseImageSelect:
1591 // set second corner of selection
1592 if ( d->mouseSelecting )
1593 selectionEndPoint( e->pos() );
1594 break;
1595 case MouseTextSelect:
1596 // if mouse moves 5 px away from the press point and the document soupports text extraction, do 'textselection'
1597 if ( !d->mouseTextSelecting && !d->mousePressPos.isNull() && d->document->supportsSearching() && ( ( e->pos() - d->mouseSelectPos ).manhattanLength() > 5 ) )
1599 d->mouseTextSelecting = true;
1601 if ( d->mouseTextSelecting )
1603 int first = -1;
1604 QList< Okular::RegularAreaRect * > selections = textSelections( e->pos(), d->mouseSelectPos, first );
1605 QSet< int > pagesWithSelectionSet;
1606 for ( int i = 0; i < selections.count(); ++i )
1607 pagesWithSelectionSet.insert( i + first );
1609 QSet< int > noMoreSelectedPages = d->pagesWithTextSelection - pagesWithSelectionSet;
1610 // clear the selection from pages not selected anymore
1611 foreach( int p, noMoreSelectedPages )
1613 d->document->setPageTextSelection( p, 0, QColor() );
1615 // set the new selection for the selected pages
1616 foreach( int p, pagesWithSelectionSet )
1618 d->document->setPageTextSelection( p, selections[ p - first ], palette().color( QPalette::Active, QPalette::Highlight ) );
1620 d->pagesWithTextSelection = pagesWithSelectionSet;
1622 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
1623 break;
1627 void PageView::contentsMousePressEvent( QMouseEvent * e )
1629 // don't perform any mouse action when no document is shown
1630 if ( d->items.isEmpty() )
1631 return;
1633 // if performing a selection or dyn zooming, disable mouse press
1634 if ( d->mouseSelecting || d->mouseMidZooming || d->viewportMoveActive )
1635 return;
1637 // if the page is scrolling, stop it
1638 if ( d->autoScrollTimer )
1640 d->scrollIncrement = 0;
1641 d->autoScrollTimer->stop();
1644 // if pressing mid mouse button while not doing other things, begin 'continuous zoom' mode
1645 if ( e->button() == Qt::MidButton )
1647 d->mouseMidZooming = true;
1648 d->mouseMidLastY = e->globalPos().y();
1649 setCursor( Qt::SizeVerCursor );
1650 return;
1653 // if we're editing an annotation, dispatch event to it
1654 if ( d->annotator && d->annotator->routeEvents() )
1656 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1657 d->annotator->routeEvent( e, pageItem );
1658 return;
1661 // update press / 'start drag' mouse position
1662 d->mousePressPos = e->globalPos();
1664 // handle mode dependant mouse press actions
1665 bool leftButton = e->button() == Qt::LeftButton,
1666 rightButton = e->button() == Qt::RightButton;
1668 // Not sure we should erase the selection when clicking with left.
1669 if ( d->mouseMode != MouseTextSelect )
1670 textSelectionClear();
1672 switch ( d->mouseMode )
1674 case MouseNormal: // drag start / click / link following
1675 if ( leftButton )
1677 PageViewItem * pageItem = 0;
1678 if ( ( e->modifiers() & Qt::ControlModifier ) && ( pageItem = pickItemOnPoint( e->x(), e->y() ) ) )
1680 // find out normalized mouse coords inside current item
1681 const QRect & itemRect = pageItem->uncroppedGeometry();
1682 double nX = pageItem->absToPageX(e->x());
1683 double nY = pageItem->absToPageY(e->y());
1684 const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() );
1685 d->mouseAnnPos = QPoint( e->x(), e->y() ) - itemRect.topLeft();
1686 if ( orect )
1687 d->mouseAnn = ( (Okular::AnnotationObjectRect *)orect )->annotation();
1688 // consider no annotation caught if its type is not movable
1689 if ( d->mouseAnn && !d->mouseAnn->canBeMoved() )
1690 d->mouseAnn = 0;
1692 if ( !d->mouseAnn )
1694 d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mousePressPos;
1695 if ( !d->mouseOnRect )
1696 setCursor( Qt::SizeAllCursor );
1699 else if ( rightButton )
1701 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1702 if ( pageItem )
1704 // find out normalized mouse coords inside current item
1705 const QRect & itemRect = pageItem->uncroppedGeometry();
1706 double nX = pageItem->absToPageX(e->x());
1707 double nY = pageItem->absToPageY(e->y());
1708 Okular::Annotation * ann = 0;
1709 const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() );
1710 if ( orect )
1711 ann = ( (Okular::AnnotationObjectRect *)orect )->annotation();
1712 if ( ann )
1714 AnnotationPopup popup( d->document, this );
1715 popup.addAnnotation( ann, pageItem->pageNumber() );
1717 connect( &popup, SIGNAL( setAnnotationWindow( Okular::Annotation* ) ),
1718 this, SLOT( setAnnotationWindow( Okular::Annotation* ) ) );
1719 connect( &popup, SIGNAL( removeAnnotationWindow( Okular::Annotation* ) ),
1720 this, SLOT( removeAnnotationWindow( Okular::Annotation* ) ) );
1722 popup.exec( e->globalPos() );
1726 break;
1728 case MouseZoom: // set first corner of the zoom rect
1729 if ( leftButton )
1730 selectionStart( e->pos(), palette().color( QPalette::Active, QPalette::Highlight ), false );
1731 else if ( rightButton )
1732 updateZoom( ZoomOut );
1733 break;
1735 case MouseSelect: // set first corner of the selection rect
1736 case MouseImageSelect:
1737 if ( leftButton )
1739 selectionStart( e->pos(), palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false );
1741 break;
1742 case MouseTextSelect:
1743 d->mouseSelectPos = e->pos();
1744 if ( !rightButton )
1746 textSelectionClear();
1748 break;
1752 void PageView::contentsMouseReleaseEvent( QMouseEvent * e )
1754 // stop the drag scrolling
1755 d->dragScrollTimer.stop();
1757 // don't perform any mouse action when no document is shown..
1758 if ( d->items.isEmpty() )
1760 // ..except for right Clicks (emitted even it viewport is empty)
1761 if ( e->button() == Qt::RightButton )
1762 emit rightClick( 0, e->globalPos() );
1763 return;
1766 // don't perform any mouse action when viewport is autoscrolling
1767 if ( d->viewportMoveActive )
1768 return;
1770 // handle mode indepent mid buttom zoom
1771 if ( d->mouseMidZooming && (e->button() == Qt::MidButton) )
1773 d->mouseMidZooming = false;
1774 // request pixmaps since it was disabled during drag
1775 slotRequestVisiblePixmaps();
1776 // the cursor may now be over a link.. update it
1777 updateCursor( e->pos() );
1778 return;
1781 // if we're editing an annotation, dispatch event to it
1782 if ( d->annotator && d->annotator->routeEvents() )
1784 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1785 d->annotator->routeEvent( e, pageItem );
1786 return;
1789 if ( d->mouseAnn )
1791 setCursor( Qt::ArrowCursor );
1792 d->mouseAnn = 0;
1795 bool leftButton = e->button() == Qt::LeftButton;
1796 bool rightButton = e->button() == Qt::RightButton;
1797 switch ( d->mouseMode )
1799 case MouseNormal:{
1800 // return the cursor to its normal state after dragging
1801 if ( cursor().shape() == Qt::SizeAllCursor )
1802 updateCursor( e->pos() );
1804 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1806 // if the mouse has not moved since the press, that's a -click-
1807 if ( leftButton && pageItem && d->mousePressPos == e->globalPos())
1809 double nX = pageItem->absToPageX(e->x());
1810 double nY = pageItem->absToPageY(e->y());
1811 const Okular::ObjectRect * rect;
1812 rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
1813 if ( rect )
1815 // handle click over a link
1816 const Okular::Action * action = static_cast< const Okular::Action * >( rect->object() );
1817 d->document->processAction( action );
1819 else
1821 // TODO: find a better way to activate the source reference "links"
1822 // for the moment they are activated with Shift + left click
1823 rect = e->modifiers() == Qt::ShiftModifier ? pageItem->page()->objectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ) : 0;
1824 if ( rect )
1826 const Okular::SourceReference * ref = static_cast< const Okular::SourceReference * >( rect->object() );
1827 d->document->processSourceReference( ref );
1829 #if 0
1830 // a link can move us to another page or even to another document, there's no point in trying to
1831 // process the click on the image once we have processes the click on the link
1832 rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->width(), pageItem->height() );
1833 if ( rect )
1835 // handle click over a image
1837 /* Enrico and me have decided this is not worth the trouble it generates
1838 else
1840 // if not on a rect, the click selects the page
1841 // if ( pageItem->pageNumber() != (int)d->document->currentPage() )
1842 d->document->setViewportPage( pageItem->pageNumber(), PAGEVIEW_ID );
1844 #endif
1847 else if ( rightButton )
1849 if ( pageItem && d->mousePressPos == e->globalPos() )
1851 double nX = pageItem->absToPageX(e->x());
1852 double nY = pageItem->absToPageY(e->y());
1853 const Okular::ObjectRect * rect;
1854 rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
1855 if ( rect )
1857 // handle right click over a link
1858 const Okular::Action * link = static_cast< const Okular::Action * >( rect->object() );
1859 // creating the menu and its actions
1860 KMenu menu( this );
1861 QAction * actProcessLink = menu.addAction( i18n( "Follow This Link" ) );
1862 QAction * actCopyLinkLocation = 0;
1863 if ( dynamic_cast< const Okular::BrowseAction * >( link ) )
1864 actCopyLinkLocation = menu.addAction( KIcon( "edit-copy" ), i18n( "Copy Link Address" ) );
1865 QAction * res = menu.exec( e->globalPos() );
1866 if ( res )
1868 if ( res == actProcessLink )
1870 d->document->processAction( link );
1872 else if ( res == actCopyLinkLocation )
1874 const Okular::BrowseAction * browseLink = static_cast< const Okular::BrowseAction * >( link );
1875 QClipboard *cb = QApplication::clipboard();
1876 cb->setText( browseLink->url(), QClipboard::Clipboard );
1877 if ( cb->supportsSelection() )
1878 cb->setText( browseLink->url(), QClipboard::Selection );
1882 else
1884 // a link can move us to another page or even to another document, there's no point in trying to
1885 // process the click on the image once we have processes the click on the link
1886 rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
1887 if ( rect )
1889 // handle right click over a image
1891 else
1893 // right click (if not within 5 px of the press point, the mode
1894 // had been already changed to 'Selection' instead of 'Normal')
1895 emit rightClick( pageItem->page(), e->globalPos() );
1899 else
1901 // right click (if not within 5 px of the press point, the mode
1902 // had been already changed to 'Selection' instead of 'Normal')
1903 emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() );
1906 }break;
1908 case MouseZoom:
1909 // if a selection rect has been defined, zoom into it
1910 if ( leftButton && d->mouseSelecting )
1912 QRect selRect = d->mouseSelectionRect.normalized();
1913 if ( selRect.width() <= 8 && selRect.height() <= 8 )
1915 selectionClear();
1916 break;
1919 // find out new zoom ratio and normalized view center (relative to the contentsRect)
1920 double zoom = qMin( (double)viewport()->width() / (double)selRect.width(), (double)viewport()->height() / (double)selRect.height() );
1921 double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)widget()->width());
1922 double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)widget()->height());
1924 // zoom up to 400%
1925 if ( d->zoomFactor <= 4.0 || zoom <= 1.0 )
1927 d->zoomFactor *= zoom;
1928 viewport()->setUpdatesEnabled( false );
1929 updateZoom( ZoomRefreshCurrent );
1930 viewport()->setUpdatesEnabled( true );
1933 // recenter view and update the viewport
1934 center( (int)(nX * widget()->width()), (int)(nY * widget()->height()) );
1935 widget()->update();
1937 // hide message box and delete overlay window
1938 selectionClear();
1940 break;
1942 case MouseSelect:
1943 case MouseImageSelect:
1945 // if mouse is released and selection is null this is a rightClick
1946 if ( rightButton && !d->mouseSelecting )
1948 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1949 emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() );
1950 break;
1953 // if a selection is defined, display a popup
1954 if ( (!leftButton && !d->aPrevAction) || (!rightButton && d->aPrevAction) ||
1955 !d->mouseSelecting )
1956 break;
1958 QRect selectionRect = d->mouseSelectionRect.normalized();
1959 if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 )
1961 selectionClear();
1962 if ( d->aPrevAction )
1964 d->aPrevAction->trigger();
1965 d->aPrevAction = 0;
1967 break;
1970 // if we support text generation
1971 QString selectedText;
1972 if (d->document->supportsSearching())
1974 // grab text in selection by extracting it from all intersected pages
1975 const Okular::Page * okularPage=0;
1976 QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd();
1977 for ( ; iIt != iEnd; ++iIt )
1979 PageViewItem * item = *iIt;
1980 if ( !item->isVisible() )
1981 continue;
1983 const QRect & itemRect = item->croppedGeometry();
1984 if ( selectionRect.intersects( itemRect ) )
1986 // request the textpage if there isn't one
1987 okularPage= item->page();
1988 kWarning() << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage();
1989 if ( !okularPage->hasTextPage() )
1990 d->document->requestTextPage( okularPage->number() );
1991 // grab text in the rect that intersects itemRect
1992 QRect relativeRect = selectionRect.intersect( itemRect );
1993 relativeRect.translate( -item->uncroppedGeometry().topLeft() );
1994 Okular::RegularAreaRect rects;
1995 rects.append( Okular::NormalizedRect( relativeRect, item->uncroppedWidth(), item->uncroppedHeight() ) );
1996 selectedText += okularPage->text( &rects );
2001 // popup that ask to copy:text and copy/save:image
2002 KMenu menu( this );
2003 QAction *textToClipboard = 0, *speakText = 0, *imageToClipboard = 0, *imageToFile = 0;
2004 if ( d->document->supportsSearching() && !selectedText.isEmpty() )
2006 menu.addTitle( i18np( "Text (1 character)", "Text (%1 characters)", selectedText.length() ) );
2007 textToClipboard = menu.addAction( KIcon("edit-copy"), i18n( "Copy to Clipboard" ) );
2008 if ( !d->document->isAllowed( Okular::AllowCopy ) )
2010 textToClipboard->setEnabled( false );
2011 textToClipboard->setText( i18n("Copy forbidden by DRM") );
2013 if ( Okular::Settings::useKTTSD() )
2014 speakText = menu.addAction( KIcon("text-speak"), i18n( "Speak Text" ) );
2016 menu.addTitle( i18n( "Image (%1 by %2 pixels)", selectionRect.width(), selectionRect.height() ) );
2017 imageToClipboard = menu.addAction( KIcon("image-x-generic"), i18n( "Copy to Clipboard" ) );
2018 imageToFile = menu.addAction( KIcon("document-save"), i18n( "Save to File..." ) );
2019 QAction *choice = menu.exec( e->globalPos() );
2020 // check if the user really selected an action
2021 if ( choice )
2023 // IMAGE operation chosen
2024 if ( choice == imageToClipboard || choice == imageToFile )
2026 // renders page into a pixmap
2027 QPixmap copyPix( selectionRect.width(), selectionRect.height() );
2028 QPainter copyPainter( &copyPix );
2029 copyPainter.translate( -selectionRect.left(), -selectionRect.top() );
2030 drawDocumentOnPainter( selectionRect, &copyPainter );
2031 copyPainter.end();
2033 if ( choice == imageToClipboard )
2035 // [2] copy pixmap to clipboard
2036 QClipboard *cb = QApplication::clipboard();
2037 cb->setPixmap( copyPix, QClipboard::Clipboard );
2038 if ( cb->supportsSelection() )
2039 cb->setPixmap( copyPix, QClipboard::Selection );
2040 d->messageWindow->display( i18n( "Image [%1x%2] copied to clipboard.", copyPix.width(), copyPix.height() ) );
2042 else if ( choice == imageToFile )
2044 // [3] save pixmap to file
2045 QString fileName = KFileDialog::getSaveFileName( KUrl(), "image/png image/jpeg", this );
2046 if ( fileName.isEmpty() )
2047 d->messageWindow->display( i18n( "File not saved." ), PageViewMessage::Warning );
2048 else
2050 KMimeType::Ptr mime = KMimeType::findByUrl( fileName );
2051 QString type;
2052 if ( !mime || mime == KMimeType::defaultMimeTypePtr() )
2053 type = "PNG";
2054 else
2055 type = mime->name().section( '/', -1 ).toUpper();
2056 copyPix.save( fileName, qPrintable( type ) );
2057 d->messageWindow->display( i18n( "Image [%1x%2] saved to %3 file.", copyPix.width(), copyPix.height(), type ) );
2061 // TEXT operation chosen
2062 else
2064 if ( choice == textToClipboard )
2066 // [1] copy text to clipboard
2067 QClipboard *cb = QApplication::clipboard();
2068 cb->setText( selectedText, QClipboard::Clipboard );
2069 if ( cb->supportsSelection() )
2070 cb->setText( selectedText, QClipboard::Selection );
2072 else if ( choice == speakText )
2074 // [2] speech selection using KTTSD
2075 d->tts()->say( selectedText );
2079 // clear widget selection and invalidate rect
2080 selectionClear();
2082 // restore previous action if came from it using right button
2083 if ( d->aPrevAction )
2085 d->aPrevAction->trigger();
2086 d->aPrevAction = 0;
2088 }break;
2089 case MouseTextSelect:
2090 setCursor( Qt::ArrowCursor );
2091 if ( d->mouseTextSelecting )
2093 d->mouseTextSelecting = false;
2094 // textSelectionClear();
2095 if ( d->document->isAllowed( Okular::AllowCopy ) )
2097 const QString text = d->selectedText();
2098 if ( !text.isEmpty() )
2100 QClipboard *cb = QApplication::clipboard();
2101 if ( cb->supportsSelection() )
2102 cb->setText( text, QClipboard::Selection );
2106 else if ( !d->mousePressPos.isNull() && rightButton )
2108 KMenu menu( this );
2109 QAction *textToClipboard = menu.addAction( KIcon( "edit-copy" ), i18n( "Copy Text" ) );
2110 QAction *speakText = 0;
2111 if ( Okular::Settings::useKTTSD() )
2112 speakText = menu.addAction( KIcon( "text-speak" ), i18n( "Speak Text" ) );
2113 if ( !d->document->isAllowed( Okular::AllowCopy ) )
2115 textToClipboard->setEnabled( false );
2116 textToClipboard->setText( i18n("Copy forbidden by DRM") );
2118 QAction *choice = menu.exec( e->globalPos() );
2119 // check if the user really selected an action
2120 if ( choice )
2122 if ( choice == textToClipboard )
2123 copyTextSelection();
2124 else if ( choice == speakText )
2126 const QString text = d->selectedText();
2127 d->tts()->say( text );
2131 break;
2134 // reset mouse press / 'drag start' position
2135 d->mousePressPos = QPoint();
2138 void PageView::wheelEvent( QWheelEvent *e )
2140 // don't perform any mouse action when viewport is autoscrolling
2141 if ( d->viewportMoveActive )
2142 return;
2144 if ( !d->document->isOpened() )
2146 QScrollArea::wheelEvent( e );
2147 return;
2150 int delta = e->delta(),
2151 vScroll = verticalScrollBar()->value();
2152 e->accept();
2153 if ( (e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) {
2154 if ( e->delta() < 0 )
2155 slotZoomOut();
2156 else
2157 slotZoomIn();
2159 else if ( delta <= -120 && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->maximum() )
2161 // go to next page
2162 if ( (int)d->document->currentPage() < d->items.count() - 1 )
2164 // more optimized than document->setNextPage and then move view to top
2165 Okular::DocumentViewport newViewport = d->document->viewport();
2166 newViewport.pageNumber += d->document->currentPage() ? viewColumns() : 1;
2167 if ( newViewport.pageNumber >= (int)d->items.count() )
2168 newViewport.pageNumber = d->items.count() - 1;
2169 newViewport.rePos.enabled = true;
2170 newViewport.rePos.normalizedY = 0.0;
2171 d->document->setViewport( newViewport );
2174 else if ( delta >= 120 && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->minimum() )
2176 // go to prev page
2177 if ( d->document->currentPage() > 0 )
2179 // more optimized than document->setPrevPage and then move view to bottom
2180 Okular::DocumentViewport newViewport = d->document->viewport();
2181 newViewport.pageNumber -= viewColumns();
2182 if ( newViewport.pageNumber < 0 )
2183 newViewport.pageNumber = 0;
2184 newViewport.rePos.enabled = true;
2185 newViewport.rePos.normalizedY = 1.0;
2186 d->document->setViewport( newViewport );
2189 else
2190 QScrollArea::wheelEvent( e );
2192 QPoint cp = widget()->mapFromGlobal(mapToGlobal(e->pos()));
2193 updateCursor(cp);
2196 void PageView::dragEnterEvent( QDragEnterEvent * ev )
2198 ev->accept();
2201 void PageView::dragMoveEvent( QDragMoveEvent * ev )
2203 ev->accept();
2206 void PageView::dropEvent( QDropEvent * ev )
2208 if ( KUrl::List::canDecode( ev->mimeData() ) )
2209 emit urlDropped( KUrl::List::fromMimeData( ev->mimeData() ).first() );
2211 //END widget events
2213 QList< Okular::RegularAreaRect * > PageView::textSelections( const QPoint& start, const QPoint& end, int& firstpage )
2215 firstpage = -1;
2216 QList< Okular::RegularAreaRect * > ret;
2217 QSet< int > affectedItemsSet;
2218 QRect selectionRect = QRect( start, end ).normalized();
2219 foreach( PageViewItem * item, d->items )
2221 if ( item->isVisible() && selectionRect.intersects( item->croppedGeometry() ) )
2222 affectedItemsSet.insert( item->pageNumber() );
2224 #ifdef PAGEVIEW_DEBUG
2225 kDebug() << ">>>> item selected by mouse:" << affectedItemsSet.count();
2226 #endif
2228 if ( !affectedItemsSet.isEmpty() )
2230 // is the mouse drag line the ne-sw diagonal of the selection rect?
2231 bool direction_ne_sw = start == selectionRect.topRight() || start == selectionRect.bottomLeft();
2233 int tmpmin = d->document->pages();
2234 int tmpmax = 0;
2235 foreach( int p, affectedItemsSet )
2237 if ( p < tmpmin ) tmpmin = p;
2238 if ( p > tmpmax ) tmpmax = p;
2241 PageViewItem * a = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.right() : selectionRect.left() ), (int)selectionRect.top() );
2242 int min = a && ( a->pageNumber() != tmpmax ) ? a->pageNumber() : tmpmin;
2243 PageViewItem * b = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.left() : selectionRect.right() ), (int)selectionRect.bottom() );
2244 int max = b && ( b->pageNumber() != tmpmin ) ? b->pageNumber() : tmpmax;
2246 QList< int > affectedItemsIds;
2247 for ( int i = min; i <= max; ++i )
2248 affectedItemsIds.append( i );
2249 #ifdef PAGEVIEW_DEBUG
2250 kDebug() << ">>>> pages:" << affectedItemsIds;
2251 #endif
2252 firstpage = affectedItemsIds.first();
2254 if ( affectedItemsIds.count() == 1 )
2256 PageViewItem * item = d->items[ affectedItemsIds.first() ];
2257 selectionRect.translate( -item->uncroppedGeometry().topLeft() );
2258 ret.append( textSelectionForItem( item,
2259 direction_ne_sw ? selectionRect.topRight() : selectionRect.topLeft(),
2260 direction_ne_sw ? selectionRect.bottomLeft() : selectionRect.bottomRight() ) );
2262 else if ( affectedItemsIds.count() > 1 )
2264 // first item
2265 PageViewItem * first = d->items[ affectedItemsIds.first() ];
2266 QRect geom = first->croppedGeometry().intersect( selectionRect ).translated( -first->uncroppedGeometry().topLeft() );
2267 ret.append( textSelectionForItem( first,
2268 selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.topRight() : geom.topLeft() ) : ( direction_ne_sw ? geom.bottomRight() : geom.bottomLeft() ),
2269 QPoint() ) );
2270 // last item
2271 PageViewItem * last = d->items[ affectedItemsIds.last() ];
2272 geom = last->croppedGeometry().intersect( selectionRect ).translated( -last->uncroppedGeometry().topLeft() );
2273 // the last item needs to appended at last...
2274 Okular::RegularAreaRect * lastArea = textSelectionForItem( last,
2275 QPoint(),
2276 selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.bottomLeft() : geom.bottomRight() ) : ( direction_ne_sw ? geom.topLeft() : geom.topRight() ) );
2277 affectedItemsIds.removeFirst();
2278 affectedItemsIds.removeLast();
2279 // item between the two above
2280 foreach( int page, affectedItemsIds )
2282 ret.append( textSelectionForItem( d->items[ page ] ) );
2284 ret.append( lastArea );
2287 return ret;
2291 void PageView::drawDocumentOnPainter( const QRect & contentsRect, QPainter * p )
2293 // when checking if an Item is contained in contentsRect, instead of
2294 // growing PageViewItems rects (for keeping outline into account), we
2295 // grow the contentsRect
2296 QRect checkRect = contentsRect;
2297 checkRect.adjust( -3, -3, 1, 1 );
2299 // create a region from which we'll subtract painted rects
2300 QRegion remainingArea( contentsRect );
2302 // iterate over all items painting the ones intersecting contentsRect
2303 QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd();
2304 for ( ; iIt != iEnd; ++iIt )
2306 // check if a piece of the page intersects the contents rect
2307 if ( !(*iIt)->isVisible() || !(*iIt)->croppedGeometry().intersects( checkRect ) )
2308 continue;
2310 // get item and item's outline geometries
2311 PageViewItem * item = *iIt;
2312 QRect itemGeometry = item->croppedGeometry(),
2313 outlineGeometry = itemGeometry;
2314 outlineGeometry.adjust( -1, -1, 3, 3 );
2316 // move the painter to the top-left corner of the real page
2317 p->save();
2318 p->translate( itemGeometry.left(), itemGeometry.top() );
2320 // draw the page outline (black border and 2px bottom-right shadow)
2321 if ( !itemGeometry.contains( contentsRect ) )
2323 int itemWidth = itemGeometry.width(),
2324 itemHeight = itemGeometry.height();
2325 // draw simple outline
2326 p->setPen( Qt::black );
2327 p->drawRect( -1, -1, itemWidth + 1, itemHeight + 1 );
2328 // draw bottom/right gradient
2329 static int levels = 2;
2330 int r = QColor(Qt::gray).red() / (levels + 2),
2331 g = QColor(Qt::gray).green() / (levels + 2),
2332 b = QColor(Qt::gray).blue() / (levels + 2);
2333 for ( int i = 0; i < levels; i++ )
2335 p->setPen( QColor( r * (i+2), g * (i+2), b * (i+2) ) );
2336 p->drawLine( i, i + itemHeight + 1, i + itemWidth + 1, i + itemHeight + 1 );
2337 p->drawLine( i + itemWidth + 1, i, i + itemWidth + 1, i + itemHeight );
2338 p->setPen( Qt::gray );
2339 p->drawLine( -1, i + itemHeight + 1, i - 1, i + itemHeight + 1 );
2340 p->drawLine( i + itemWidth + 1, -1, i + itemWidth + 1, i - 1 );
2344 // draw the page using the PagePainter with all flags active
2345 if ( contentsRect.intersects( itemGeometry ) )
2347 QRect pixmapRect = contentsRect.intersect( itemGeometry );
2348 pixmapRect.translate( -item->croppedGeometry().topLeft() );
2349 PagePainter::paintCroppedPageOnPainter( p, item->page(), PAGEVIEW_ID, pageflags,
2350 item->uncroppedWidth(), item->uncroppedHeight(), pixmapRect,
2351 item->crop() );
2354 // remove painted area from 'remainingArea' and restore painter
2355 remainingArea -= outlineGeometry.intersect( contentsRect );
2356 p->restore();
2359 // fill with background color the unpainted area
2360 const QVector<QRect> &backRects = remainingArea.rects();
2361 int backRectsNumber = backRects.count();
2362 // the previous color here was Qt::gray
2363 QColor backColor = widget()->palette().color( QPalette::Dark );
2364 for ( int jr = 0; jr < backRectsNumber; jr++ )
2365 p->fillRect( backRects[ jr ], backColor );
2368 void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight )
2370 const Okular::Page * okularPage = item->page();
2371 double width = okularPage->width(),
2372 height = okularPage->height(),
2373 zoom = d->zoomFactor;
2374 Okular::NormalizedRect crop( 0., 0., 1., 1. );
2376 // Handle cropping
2377 if ( Okular::Settings::trimMargins() && okularPage->isBoundingBoxKnown()
2378 && !okularPage->boundingBox().isNull() )
2380 crop = okularPage->boundingBox();
2382 // Rotate the bounding box from upright Rotation0 to current page orientation:
2383 for ( int i = okularPage->totalOrientation(); i > 0; --i )
2385 Okular::NormalizedRect rot = crop;
2386 crop.left = 1 - rot.bottom;
2387 crop.top = rot.left;
2388 crop.right = 1 - rot.top;
2389 crop.bottom = rot.right;
2392 // Expand the crop slightly beyond the bounding box
2393 static const double cropExpandRatio = 0.04;
2394 double cropExpand = cropExpandRatio * ( (crop.right-crop.left) + (crop.bottom-crop.top) ) / 2;
2395 crop = Okular::NormalizedRect(
2396 crop.left - cropExpand,
2397 crop.top - cropExpand,
2398 crop.right + cropExpand,
2399 crop.bottom + cropExpand ) & Okular::NormalizedRect( 0, 0, 1, 1 );
2401 // We currently generate a larger image and then crop it, so if the
2402 // crop rect is very small the generated image is huge. Hence, we shouldn't
2403 // let the crop rect become too small.
2404 // Make sure we crop by at most 50% in either dimension:
2405 static const double minCropRatio = 0.5;
2406 if ( ( crop.right - crop.left ) < minCropRatio )
2408 double newLeft = ( crop.left + crop.right ) / 2 - minCropRatio/2;
2409 crop.left = qMax( 0.0, qMin( 1.0 - minCropRatio, newLeft ) );
2410 crop.right = crop.left + minCropRatio;
2412 if ( ( crop.bottom - crop.top ) < minCropRatio )
2414 double newTop = ( crop.top + crop.bottom ) / 2 - minCropRatio/2;
2415 crop.top = qMax( 0.0, qMin( 1.0 - minCropRatio, newTop ) );
2416 crop.bottom = crop.top + minCropRatio;
2419 width *= ( crop.right - crop.left );
2420 height *= ( crop.bottom - crop.top );
2421 #ifdef PAGEVIEW_DEBUG
2422 kDebug() << "Cropped page" << okularPage->number() << "to" << crop
2423 << "width" << width << "height" << height << "by bbox" << okularPage->boundingBox();
2424 #endif
2427 if ( d->zoomMode == ZoomFixed )
2429 width *= zoom;
2430 height *= zoom;
2431 item->setWHZC( (int)width, (int)height, d->zoomFactor, crop );
2433 else if ( d->zoomMode == ZoomFitWidth )
2435 height = ( height / width ) * colWidth;
2436 zoom = (double)colWidth / width;
2437 item->setWHZC( colWidth, (int)height, zoom, crop );
2438 d->zoomFactor = zoom;
2440 else if ( d->zoomMode == ZoomFitPage )
2442 double scaleW = (double)colWidth / (double)width;
2443 double scaleH = (double)rowHeight / (double)height;
2444 zoom = qMin( scaleW, scaleH );
2445 item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop );
2446 d->zoomFactor = zoom;
2448 #ifndef NDEBUG
2449 else
2450 kDebug() << "calling updateItemSize with unrecognized d->zoomMode!";
2451 #endif
2454 PageViewItem * PageView::pickItemOnPoint( int x, int y )
2456 PageViewItem * item = 0;
2457 QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd();
2458 for ( ; iIt != iEnd; ++iIt )
2460 PageViewItem * i = *iIt;
2461 const QRect & r = i->croppedGeometry();
2462 if ( x < r.right() && x > r.left() && y < r.bottom() )
2464 if ( y > r.top() )
2465 item = i;
2466 break;
2469 return item;
2472 void PageView::textSelectionClear()
2474 // something to clear
2475 if ( !d->pagesWithTextSelection.isEmpty() )
2477 QSet< int >::ConstIterator it = d->pagesWithTextSelection.constBegin(), itEnd = d->pagesWithTextSelection.constEnd();
2478 for ( ; it != itEnd; ++it )
2479 d->document->setPageTextSelection( *it, 0, QColor() );
2480 d->pagesWithTextSelection.clear();
2484 void PageView::selectionStart( const QPoint & pos, const QColor & color, bool /*aboveAll*/ )
2486 selectionClear();
2487 d->mouseSelecting = true;
2488 d->mouseSelectionRect.setRect( pos.x(), pos.y(), 1, 1 );
2489 d->mouseSelectionColor = color;
2490 // ensures page doesn't scroll
2491 if ( d->autoScrollTimer )
2493 d->scrollIncrement = 0;
2494 d->autoScrollTimer->stop();
2498 void PageView::selectionEndPoint( const QPoint & pos )
2500 if ( !d->mouseSelecting )
2501 return;
2503 if (pos.x() < horizontalScrollBar()->value()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value());
2504 else if (horizontalScrollBar()->value() + viewport()->width() < pos.x()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value() - viewport()->width());
2505 else d->dragScrollVector.setX(0);
2507 if (pos.y() < verticalScrollBar()->value()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value());
2508 else if (verticalScrollBar()->value() + viewport()->height() < pos.y()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value() - viewport()->height());
2509 else d->dragScrollVector.setY(0);
2511 if (d->dragScrollVector != QPoint(0, 0))
2513 if (!d->dragScrollTimer.isActive()) d->dragScrollTimer.start(100);
2515 else d->dragScrollTimer.stop();
2517 // update the selection rect
2518 QRect updateRect = d->mouseSelectionRect;
2519 d->mouseSelectionRect.setBottomLeft( pos );
2520 updateRect |= d->mouseSelectionRect;
2521 widget()->update( updateRect.adjusted( -1, -1, 1, 1 ) );
2524 static Okular::NormalizedPoint rotateInNormRect( const QPoint &rotated, const QRect &rect, Okular::Rotation rotation )
2526 Okular::NormalizedPoint ret;
2528 switch ( rotation )
2530 case Okular::Rotation0:
2531 ret = Okular::NormalizedPoint( rotated.x(), rotated.y(), rect.width(), rect.height() );
2532 break;
2533 case Okular::Rotation90:
2534 ret = Okular::NormalizedPoint( rotated.y(), rect.width() - rotated.x(), rect.height(), rect.width() );
2535 break;
2536 case Okular::Rotation180:
2537 ret = Okular::NormalizedPoint( rect.width() - rotated.x(), rect.height() - rotated.y(), rect.width(), rect.height() );
2538 break;
2539 case Okular::Rotation270:
2540 ret = Okular::NormalizedPoint( rect.height() - rotated.y(), rotated.x(), rect.height(), rect.width() );
2541 break;
2544 return ret;
2547 Okular::RegularAreaRect * PageView::textSelectionForItem( PageViewItem * item, const QPoint & startPoint, const QPoint & endPoint )
2549 const QRect & geometry = item->uncroppedGeometry();
2550 Okular::NormalizedPoint startCursor( 0.0, 0.0 );
2551 if ( !startPoint.isNull() )
2553 startCursor = rotateInNormRect( startPoint, geometry, item->page()->rotation() );
2555 Okular::NormalizedPoint endCursor( 1.0, 1.0 );
2556 if ( !endPoint.isNull() )
2558 endCursor = rotateInNormRect( endPoint, geometry, item->page()->rotation() );
2560 Okular::TextSelection mouseTextSelectionInfo( startCursor, endCursor );
2562 const Okular::Page * okularPage = item->page();
2564 if ( !okularPage->hasTextPage() )
2565 d->document->requestTextPage( okularPage->number() );
2567 Okular::RegularAreaRect * selectionArea = okularPage->textArea( &mouseTextSelectionInfo );
2568 #ifdef PAGEVIEW_DEBUG
2569 kDebug().nospace() << "text areas (" << okularPage->number() << "): " << ( selectionArea ? QString::number( selectionArea->count() ) : "(none)" );
2570 #endif
2571 return selectionArea;
2574 void PageView::selectionClear()
2576 QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( 0, 0, 1, 1 );
2577 d->mouseSelecting = false;
2578 d->mouseSelectionRect.setCoords( 0, 0, 0, 0 );
2579 widget()->update( updatedRect );
2582 void PageView::updateZoom( ZoomMode newZoomMode )
2584 if ( newZoomMode == ZoomFixed )
2586 if ( d->aZoom->currentItem() == 0 )
2587 newZoomMode = ZoomFitWidth;
2588 else if ( d->aZoom->currentItem() == 1 )
2589 newZoomMode = ZoomFitPage;
2592 float newFactor = d->zoomFactor;
2593 QAction * checkedZoomAction = 0;
2594 switch ( newZoomMode )
2596 case ZoomFixed:{ //ZoomFixed case
2597 QString z = d->aZoom->currentText();
2598 // kdelibs4 sometimes adds accelerators to actions' text directly :(
2599 z.remove ('&');
2600 z.remove ('%');
2601 newFactor = KGlobal::locale()->readNumber( z ) / 100.0;
2602 }break;
2603 case ZoomIn:
2604 newFactor += (newFactor > 0.99) ? ( newFactor > 1.99 ? 0.5 : 0.2 ) : 0.1;
2605 newZoomMode = ZoomFixed;
2606 break;
2607 case ZoomOut:
2608 newFactor -= (newFactor > 1.01) ? ( newFactor > 2.01 ? 0.5 : 0.2 ) : 0.1;
2609 newZoomMode = ZoomFixed;
2610 break;
2611 case ZoomFitWidth:
2612 checkedZoomAction = d->aZoomFitWidth;
2613 break;
2614 case ZoomFitPage:
2615 checkedZoomAction = d->aZoomFitPage;
2616 break;
2617 case ZoomFitText:
2618 checkedZoomAction = d->aZoomFitText;
2619 break;
2620 case ZoomRefreshCurrent:
2621 newZoomMode = ZoomFixed;
2622 d->zoomFactor = -1;
2623 break;
2625 if ( newFactor > 4.0 )
2626 newFactor = 4.0;
2627 if ( newFactor < 0.1 )
2628 newFactor = 0.1;
2630 if ( newZoomMode != d->zoomMode || (newZoomMode == ZoomFixed && newFactor != d->zoomFactor ) )
2632 // rebuild layout and update the whole viewport
2633 d->zoomMode = newZoomMode;
2634 d->zoomFactor = newFactor;
2635 // be sure to block updates to document's viewport
2636 bool prevState = d->blockViewport;
2637 d->blockViewport = true;
2638 slotRelayoutPages();
2639 d->blockViewport = prevState;
2640 // request pixmaps
2641 slotRequestVisiblePixmaps();
2642 // update zoom text
2643 updateZoomText();
2644 // update actions checked state
2645 if ( d->aZoomFitWidth )
2647 d->aZoomFitWidth->setChecked( checkedZoomAction == d->aZoomFitWidth );
2648 d->aZoomFitPage->setChecked( checkedZoomAction == d->aZoomFitPage );
2649 // d->aZoomFitText->setChecked( checkedZoomAction == d->aZoomFitText );
2653 d->aZoomIn->setEnabled( d->zoomFactor < 3.9 );
2654 d->aZoomOut->setEnabled( d->zoomFactor > 0.2 );
2657 void PageView::updateZoomText()
2659 // use current page zoom as zoomFactor if in ZoomFit/* mode
2660 if ( d->zoomMode != ZoomFixed && d->items.count() > 0 )
2661 d->zoomFactor = d->items[ qMax( 0, (int)d->document->currentPage() ) ]->zoomFactor();
2662 float newFactor = d->zoomFactor;
2663 d->aZoom->removeAllActions();
2665 // add items that describe fit actions
2666 QStringList translated;
2667 translated << i18n("Fit Width") << i18n("Fit Page") /*<< i18n("Fit Text")*/;
2669 // add percent items
2670 QString double_oh( "00" );
2671 const float zoomValue[10] = { 0.12, 0.25, 0.33, 0.50, 0.66, 0.75, 1.00, 1.25, 1.50, 2.00 };
2672 int idx = 0,
2673 selIdx = 2; // use 3 if "fit text" present
2674 bool inserted = false; //use: "d->zoomMode != ZoomFixed" to hide Fit/* zoom ratio
2675 while ( idx < 10 || !inserted )
2677 float value = idx < 10 ? zoomValue[ idx ] : newFactor;
2678 if ( !inserted && newFactor < (value - 0.0001) )
2679 value = newFactor;
2680 else
2681 idx ++;
2682 if ( value > (newFactor - 0.0001) && value < (newFactor + 0.0001) )
2683 inserted = true;
2684 if ( !inserted )
2685 selIdx++;
2686 QString localValue( KGlobal::locale()->formatNumber( value * 100.0, 2 ) );
2687 localValue.remove( KGlobal::locale()->decimalSymbol() + double_oh );
2688 // remove a trailing zero in numbers like 66.70
2689 if ( localValue.right( 1 ) == QLatin1String( "0" ) && localValue.indexOf( KGlobal::locale()->decimalSymbol() ) > -1 )
2690 localValue.chop( 1 );
2691 translated << QString( "%1%" ).arg( localValue );
2693 d->aZoom->setItems( translated );
2695 // select current item in list
2696 if ( d->zoomMode == ZoomFitWidth )
2697 selIdx = 0;
2698 else if ( d->zoomMode == ZoomFitPage )
2699 selIdx = 1;
2700 else if ( d->zoomMode == ZoomFitText )
2701 selIdx = 2;
2702 d->aZoom->setCurrentItem( selIdx );
2705 void PageView::updateCursor( const QPoint &p )
2707 // detect the underlaying page (if present)
2708 PageViewItem * pageItem = pickItemOnPoint( p.x(), p.y() );
2709 if ( pageItem )
2711 double nX = pageItem->absToPageX(p.x());
2712 double nY = pageItem->absToPageY(p.y());
2714 // if over a ObjectRect (of type Link) change cursor to hand
2715 if ( d->mouseMode == MouseTextSelect )
2716 setCursor( Qt::IBeamCursor );
2717 else if ( d->mouseAnn )
2718 setCursor( Qt::ClosedHandCursor );
2719 else
2721 const Okular::ObjectRect * linkobj = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
2722 const Okular::ObjectRect * annotobj = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
2723 if ( linkobj && !annotobj )
2725 d->mouseOnRect = true;
2726 setCursor( Qt::PointingHandCursor );
2728 else
2730 d->mouseOnRect = false;
2731 if ( annotobj
2732 && ( QApplication::keyboardModifiers() & Qt::ControlModifier )
2733 && static_cast< const Okular::AnnotationObjectRect * >( annotobj )->annotation()->canBeMoved() )
2735 setCursor( Qt::OpenHandCursor );
2737 else
2739 setCursor( Qt::ArrowCursor );
2744 else
2746 // if there's no page over the cursor and we were showing the pointingHandCursor
2747 // go back to the normal one
2748 d->mouseOnRect = false;
2749 setCursor( Qt::ArrowCursor );
2753 int PageView::viewColumns() const
2755 int nr = Okular::Settings::viewMode();
2756 if (nr<2)
2757 return nr+1;
2758 return Okular::Settings::viewColumns();
2761 int PageView::viewRows() const
2763 if ( Okular::Settings::viewMode() < 2 )
2764 return 1;
2765 return Okular::Settings::viewRows();
2768 void PageView::center(int cx, int cy)
2770 horizontalScrollBar()->setValue(cx - viewport()->width() / 2);
2771 verticalScrollBar()->setValue(cy - viewport()->height() / 2);
2774 void PageView::toggleFormWidgets( bool on )
2776 bool somehadfocus = false;
2777 QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd();
2778 for ( ; dIt != dEnd; ++dIt )
2780 bool hadfocus = (*dIt)->setFormWidgetsVisible( on );
2781 somehadfocus = somehadfocus || hadfocus;
2783 if ( somehadfocus )
2784 setFocus();
2785 d->m_formsVisible = on;
2786 if ( d->aToggleForms ) // it may not exist if we are on dummy mode
2788 if ( d->m_formsVisible )
2790 d->aToggleForms->setText( i18n( "Hide Forms" ) );
2792 else
2794 d->aToggleForms->setText( i18n( "Show Forms" ) );
2799 //BEGIN private SLOTS
2800 void PageView::slotRelayoutPages()
2801 // called by: notifySetup, viewportResizeEvent, slotViewMode, slotContinuousToggled, updateZoom
2803 // set an empty container if we have no pages
2804 int pageCount = d->items.count();
2805 if ( pageCount < 1 )
2807 setWidgetResizable(true);
2808 return;
2811 // if viewport was auto-moving, stop it
2812 if ( d->viewportMoveActive )
2814 center( d->viewportMoveDest.x(), d->viewportMoveDest.y() );
2815 d->viewportMoveActive = false;
2816 d->viewportMoveTimer->stop();
2817 verticalScrollBar()->setEnabled( true );
2818 horizontalScrollBar()->setEnabled( true );
2821 // common iterator used in this method and viewport parameters
2822 QVector< PageViewItem * >::const_iterator iIt, iEnd = d->items.constEnd();
2823 int viewportWidth = viewport()->width(),
2824 viewportHeight = viewport()->height(),
2825 fullWidth = 0,
2826 fullHeight = 0;
2827 QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewportWidth, viewportHeight );
2829 // handle the 'center first page in row' stuff
2830 int nCols = viewColumns();
2831 bool centerFirstPage = Okular::Settings::centerFirstPageInRow() && nCols > 1;
2832 const bool continuousView = Okular::Settings::viewContinuous();
2834 // set all items geometry and resize contents. handle 'continuous' and 'single' modes separately
2836 PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage() ) ];
2838 // handle the 'centering on first row' stuff
2839 if ( centerFirstPage )
2840 pageCount += nCols - 1;
2841 // Here we find out column's width and row's height to compute a table
2842 // so we can place widgets 'centered in virtual cells'.
2843 int nRows;
2845 // if ( Okular::Settings::viewMode() < 2 )
2846 nRows = (int)ceil( (float)pageCount / (float)nCols );
2847 // nRows=(int)ceil( (float)pageCount / (float) Okular::Settings::viewRows() );
2848 // else
2849 // nRows = Okular::Settings::viewRows();
2851 int * colWidth = new int[ nCols ],
2852 * rowHeight = new int[ nRows ],
2853 cIdx = 0,
2854 rIdx = 0;
2855 for ( int i = 0; i < nCols; i++ )
2856 colWidth[ i ] = viewportWidth / nCols;
2857 for ( int i = 0; i < nRows; i++ )
2858 rowHeight[ i ] = 0;
2859 // handle the 'centering on first row' stuff
2860 if ( centerFirstPage )
2862 pageCount -= nCols - 1;
2863 cIdx += nCols - 1;
2866 // 1) find the maximum columns width and rows height for a grid in
2867 // which each page must well-fit inside a cell
2868 for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt )
2870 PageViewItem * item = *iIt;
2871 // update internal page size (leaving a little margin in case of Fit* modes)
2872 updateItemSize( item, colWidth[ cIdx ] - 6, viewportHeight - 12 );
2873 // find row's maximum height and column's max width
2874 if ( item->croppedWidth() + 6 > colWidth[ cIdx ] )
2875 colWidth[ cIdx ] = item->croppedWidth() + 6;
2876 if ( item->croppedHeight() + 12 > rowHeight[ rIdx ] )
2877 rowHeight[ rIdx ] = item->croppedHeight() + 12;
2878 // handle the 'centering on first row' stuff
2879 // update col/row indices
2880 if ( ++cIdx == nCols )
2882 cIdx = 0;
2883 rIdx++;
2887 const int pageRowIdx = ( ( centerFirstPage ? nCols - 1 : 0 ) + currentItem->pageNumber() ) / nCols;
2889 // 2) compute full size
2890 for ( int i = 0; i < nCols; i++ )
2891 fullWidth += colWidth[ i ];
2892 if ( continuousView )
2894 for ( int i = 0; i < nRows; i++ )
2895 fullHeight += rowHeight[ i ];
2897 else
2898 fullHeight = rowHeight[ pageRowIdx ];
2900 // 3) arrange widgets inside cells (and refine fullHeight if needed)
2901 int insertX = 0,
2902 insertY = fullHeight < viewportHeight ? ( viewportHeight - fullHeight ) / 2 : 0;
2903 const int origInsertY = insertY;
2904 cIdx = 0;
2905 rIdx = 0;
2906 if ( centerFirstPage )
2908 cIdx += nCols - 1;
2909 for ( int i = 0; i < cIdx; ++i )
2910 insertX += colWidth[ i ];
2912 for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt )
2914 PageViewItem * item = *iIt;
2915 int cWidth = colWidth[ cIdx ],
2916 rHeight = rowHeight[ rIdx ];
2917 if ( continuousView || rIdx == pageRowIdx )
2919 const bool reallyDoCenterFirst = item->pageNumber() == 0 && centerFirstPage;
2920 item->moveTo( (reallyDoCenterFirst ? 0 : insertX) + ( (reallyDoCenterFirst ? fullWidth : cWidth) - item->croppedWidth()) / 2,
2921 (continuousView ? insertY : origInsertY) + (rHeight - item->croppedHeight()) / 2 );
2922 item->setVisible( true );
2924 else
2926 item->moveTo( 0, 0 );
2927 item->setVisible( false );
2929 item->setFormWidgetsVisible( d->m_formsVisible );
2930 // advance col/row index
2931 insertX += cWidth;
2932 if ( ++cIdx == nCols )
2934 cIdx = 0;
2935 rIdx++;
2936 insertX = 0;
2937 insertY += rHeight;
2939 #ifdef PAGEVIEW_DEBUG
2940 kWarning() << "updating size for pageno" << item->pageNumber() << "cropped" << item->croppedGeometry() << "uncropped" << item->uncroppedGeometry();
2941 #endif
2944 delete [] colWidth;
2945 delete [] rowHeight;
2947 // 3) reset dirty state
2948 d->dirtyLayout = false;
2950 horizontalScrollBar()->setRange( 0, qMax( 0, fullWidth - viewport()->width() ) );
2951 verticalScrollBar()->setRange( 0, qMax( 0, fullHeight - viewport()->height() ) );
2953 // 4) update scrollview's contents size and recenter view
2954 bool wasUpdatesEnabled = viewport()->updatesEnabled();
2955 if ( fullWidth != widget()->width() || fullHeight != widget()->height() )
2957 // disable updates and resize the viewportContents
2958 if ( wasUpdatesEnabled )
2959 viewport()->setUpdatesEnabled( false );
2960 setWidgetResizable(false);
2961 fullWidth = qMax(fullWidth, viewport()->width());
2962 fullHeight = qMax(fullHeight, viewport()->height());
2963 widget()->resize( fullWidth, fullHeight );
2964 // restore previous viewport if defined and updates enabled
2965 if ( wasUpdatesEnabled )
2967 const Okular::DocumentViewport & vp = d->document->viewport();
2968 if ( vp.pageNumber >= 0 )
2970 int prevX = horizontalScrollBar()->value(),
2971 prevY = verticalScrollBar()->value();
2972 const QRect & geometry = d->items[ vp.pageNumber ]->croppedGeometry();
2973 double nX = vp.rePos.enabled ? vp.rePos.normalizedX : 0.5,
2974 nY = vp.rePos.enabled ? vp.rePos.normalizedY : 0.0;
2975 center( geometry.left() + qRound( nX * (double)geometry.width() ),
2976 geometry.top() + qRound( nY * (double)geometry.height() ) );
2977 // center() usually moves the viewport, that requests pixmaps too.
2978 // if that doesn't happen we have to request them by hand
2979 if ( prevX == horizontalScrollBar()->value() && prevY == verticalScrollBar()->value() )
2980 slotRequestVisiblePixmaps();
2982 // or else go to center page
2983 else
2984 center( fullWidth / 2, 0 );
2985 viewport()->setUpdatesEnabled( true );
2989 // 5) update the whole viewport if updated enabled
2990 if ( wasUpdatesEnabled )
2991 widget()->update();
2994 void PageView::slotRequestVisiblePixmaps( int newValue )
2996 // if requests are blocked (because raised by an unwanted event), exit
2997 if ( d->blockPixmapsRequest || d->viewportMoveActive ||
2998 d->mouseMidZooming )
2999 return;
3001 // precalc view limits for intersecting with page coords inside the lOOp
3002 bool isEvent = newValue != -1 && !d->blockViewport;
3003 QRect viewportRect( horizontalScrollBar()->value(),
3004 verticalScrollBar()->value(),
3005 viewport()->width(), viewport()->height() );
3007 // some variables used to determine the viewport
3008 int nearPageNumber = -1;
3009 double viewportCenterX = (viewportRect.left() + viewportRect.right()) / 2.0,
3010 viewportCenterY = (viewportRect.top() + viewportRect.bottom()) / 2.0,
3011 focusedX = 0.5,
3012 focusedY = 0.0,
3013 minDistance = -1.0;
3015 // iterate over all items
3016 d->visibleItems.clear();
3017 QLinkedList< Okular::PixmapRequest * > requestedPixmaps;
3018 QVector< Okular::VisiblePageRect * > visibleRects;
3019 QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd();
3020 for ( ; iIt != iEnd; ++iIt )
3022 PageViewItem * i = *iIt;
3023 if ( !i->isVisible() )
3024 continue;
3025 #ifdef PAGEVIEW_DEBUG
3026 kWarning() << "checking page" << i->pageNumber();
3027 kWarning().nospace() << "viewportRect is " << viewportRect << ", page item is " << i->croppedGeometry() << " intersect : " << viewportRect.intersects( i->croppedGeometry() );
3028 #endif
3029 // if the item doesn't intersect the viewport, skip it
3030 QRect intersectionRect = viewportRect.intersect( i->croppedGeometry() );
3031 if ( intersectionRect.isEmpty() )
3032 continue;
3034 // add the item to the 'visible list'
3035 d->visibleItems.push_back( i );
3036 Okular::VisiblePageRect * vItem = new Okular::VisiblePageRect( i->pageNumber(), Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ) );
3037 visibleRects.push_back( vItem );
3038 #ifdef PAGEVIEW_DEBUG
3039 kWarning() << "checking for pixmap for page" << i->pageNumber() << "=" << i->page()->hasPixmap( PAGEVIEW_ID, i->uncroppedWidth(), i->uncroppedHeight() );
3040 kWarning() << "checking for text for page" << i->pageNumber() << "=" << i->page()->hasTextPage();
3041 #endif
3042 // if the item has not the right pixmap, add a request for it
3043 // TODO: We presently request a pixmap for the full page, and then render just the crop part. This waste memory and cycles.
3044 if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->uncroppedWidth(), i->uncroppedHeight() ) )
3046 #ifdef PAGEVIEW_DEBUG
3047 kWarning() << "rerequesting visible pixmaps for page" << i->pageNumber() << "!";
3048 #endif
3049 Okular::PixmapRequest * p = new Okular::PixmapRequest(
3050 PAGEVIEW_ID, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRIO, true );
3051 requestedPixmaps.push_back( p );
3054 // look for the item closest to viewport center and the relative
3055 // position between the item and the viewport center
3056 if ( isEvent )
3058 const QRect & geometry = i->croppedGeometry();
3059 // compute distance between item center and viewport center (slightly moved left)
3060 double distance = hypot( (geometry.left() + geometry.right()) / 2 - (viewportCenterX - 4),
3061 (geometry.top() + geometry.bottom()) / 2 - viewportCenterY );
3062 if ( distance >= minDistance && nearPageNumber != -1 )
3063 continue;
3064 nearPageNumber = i->pageNumber();
3065 minDistance = distance;
3066 if ( geometry.height() > 0 && geometry.width() > 0 )
3068 focusedX = ( viewportCenterX - (double)geometry.left() ) / (double)geometry.width();
3069 focusedY = ( viewportCenterY - (double)geometry.top() ) / (double)geometry.height();
3074 // if preloading is enabled, add the pages before and after in preloading
3075 if ( !d->visibleItems.isEmpty() &&
3076 Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Low &&
3077 Okular::Settings::enableThreading() )
3079 // as the requests are done in the order as they appear in the list,
3080 // request first the next page and then the previous
3082 // add the page after the 'visible series' in preload
3083 int tailRequest = d->visibleItems.last()->pageNumber() + 1;
3084 if ( tailRequest < (int)d->items.count() )
3086 PageViewItem * i = d->items[ tailRequest ];
3087 // request the pixmap if not already present
3088 if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->uncroppedWidth(), i->uncroppedHeight() ) && i->uncroppedWidth() > 0 )
3089 requestedPixmaps.push_back( new Okular::PixmapRequest(
3090 PAGEVIEW_ID, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, true ) );
3093 // add the page before the 'visible series' in preload
3094 int headRequest = d->visibleItems.first()->pageNumber() - 1;
3095 if ( headRequest >= 0 )
3097 PageViewItem * i = d->items[ headRequest ];
3098 // request the pixmap if not already present
3099 if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->uncroppedWidth(), i->uncroppedHeight() ) && i->uncroppedWidth() > 0 )
3100 requestedPixmaps.push_back( new Okular::PixmapRequest(
3101 PAGEVIEW_ID, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, true ) );
3105 // send requests to the document
3106 if ( !requestedPixmaps.isEmpty() )
3108 d->document->requestPixmaps( requestedPixmaps );
3110 // if this functions was invoked by viewport events, send update to document
3111 if ( isEvent && nearPageNumber != -1 )
3113 // determine the document viewport
3114 Okular::DocumentViewport newViewport( nearPageNumber );
3115 newViewport.rePos.enabled = true;
3116 newViewport.rePos.normalizedX = focusedX;
3117 newViewport.rePos.normalizedY = focusedY;
3118 // set the viewport to other observers
3119 d->document->setViewport( newViewport , PAGEVIEW_ID);
3121 d->document->setVisiblePageRects( visibleRects, PAGEVIEW_ID );
3124 void PageView::slotMoveViewport()
3126 // converge to viewportMoveDest in 1 second
3127 int diffTime = d->viewportMoveTime.elapsed();
3128 if ( diffTime >= 667 || !d->viewportMoveActive )
3130 center( d->viewportMoveDest.x(), d->viewportMoveDest.y() );
3131 d->viewportMoveTimer->stop();
3132 d->viewportMoveActive = false;
3133 slotRequestVisiblePixmaps();
3134 verticalScrollBar()->setEnabled( true );
3135 horizontalScrollBar()->setEnabled( true );
3136 return;
3139 // move the viewport smoothly (kmplot: p(x)=1+0.47*(x-1)^3-0.25*(x-1)^4)
3140 float convergeSpeed = (float)diffTime / 667.0,
3141 x = ((float)viewport()->width() / 2.0) + horizontalScrollBar()->value(),
3142 y = ((float)viewport()->height() / 2.0) + verticalScrollBar()->value(),
3143 diffX = (float)d->viewportMoveDest.x() - x,
3144 diffY = (float)d->viewportMoveDest.y() - y;
3145 convergeSpeed *= convergeSpeed * (1.4 - convergeSpeed);
3146 center( (int)(x + diffX * convergeSpeed),
3147 (int)(y + diffY * convergeSpeed ) );
3150 void PageView::slotAutoScoll()
3152 // the first time create the timer
3153 if ( !d->autoScrollTimer )
3155 d->autoScrollTimer = new QTimer( this );
3156 d->autoScrollTimer->setSingleShot( true );
3157 connect( d->autoScrollTimer, SIGNAL( timeout() ), this, SLOT( slotAutoScoll() ) );
3160 // if scrollIncrement is zero, stop the timer
3161 if ( !d->scrollIncrement )
3163 d->autoScrollTimer->stop();
3164 return;
3167 // compute delay between timer ticks and scroll amount per tick
3168 int index = abs( d->scrollIncrement ) - 1; // 0..9
3169 const int scrollDelay[10] = { 200, 100, 50, 30, 20, 30, 25, 20, 30, 20 };
3170 const int scrollOffset[10] = { 1, 1, 1, 1, 1, 2, 2, 2, 4, 4 };
3171 d->autoScrollTimer->start( scrollDelay[ index ] );
3172 int delta = d->scrollIncrement > 0 ? scrollOffset[ index ] : -scrollOffset[ index ];
3173 verticalScrollBar()->setValue(verticalScrollBar()->value() + delta);
3176 void PageView::slotDragScroll()
3178 horizontalScrollBar()->setValue(horizontalScrollBar()->value() + d->dragScrollVector.x());
3179 verticalScrollBar()->setValue(verticalScrollBar()->value() + d->dragScrollVector.y());
3180 QPoint p = widget()->mapFromGlobal( QCursor::pos() );
3181 selectionEndPoint( p );
3184 void PageView::slotShowWelcome()
3186 // show initial welcome text
3187 d->messageWindow->display( i18n( "Welcome" ), PageViewMessage::Info, 2000 );
3190 void PageView::slotZoom()
3192 setFocus();
3193 updateZoom( ZoomFixed );
3196 void PageView::slotZoomIn()
3198 updateZoom( ZoomIn );
3201 void PageView::slotZoomOut()
3203 updateZoom( ZoomOut );
3206 void PageView::slotFitToWidthToggled( bool on )
3208 if ( on ) updateZoom( ZoomFitWidth );
3211 void PageView::slotFitToPageToggled( bool on )
3213 if ( on ) updateZoom( ZoomFitPage );
3216 void PageView::slotFitToTextToggled( bool on )
3218 if ( on ) updateZoom( ZoomFitText );
3221 void PageView::slotViewMode( QAction *action )
3223 const int nr = action->data().toInt();
3224 if ( (int)Okular::Settings::viewMode() != nr )
3226 Okular::Settings::setViewMode( nr );
3227 Okular::Settings::self()->writeConfig();
3228 if ( d->document->pages() > 0 )
3229 slotRelayoutPages();
3233 void PageView::slotContinuousToggled( bool on )
3235 if ( Okular::Settings::viewContinuous() != on )
3237 Okular::Settings::setViewContinuous( on );
3238 Okular::Settings::self()->writeConfig();
3239 if ( d->document->pages() > 0 )
3240 slotRelayoutPages();
3244 void PageView::slotSetMouseNormal()
3246 d->mouseMode = MouseNormal;
3247 // hide the messageWindow
3248 d->messageWindow->hide();
3249 // reshow the annotator toolbar if hiding was forced
3250 if ( d->aToggleAnnotator->isChecked() )
3251 slotToggleAnnotator( true );
3252 // force an update of the cursor
3253 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
3256 void PageView::slotSetMouseZoom()
3258 d->mouseMode = MouseZoom;
3259 // change the text in messageWindow (and show it if hidden)
3260 d->messageWindow->display( i18n( "Select zooming area. Right-click to zoom out." ), PageViewMessage::Info, -1 );
3261 // force hiding of annotator toolbar
3262 if ( d->annotator )
3263 d->annotator->setEnabled( false );
3264 // force an update of the cursor
3265 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
3268 void PageView::slotSetMouseSelect()
3270 d->mouseMode = MouseSelect;
3271 // change the text in messageWindow (and show it if hidden)
3272 d->messageWindow->display( i18n( "Draw a rectangle around the text/graphics to copy." ), PageViewMessage::Info, -1 );
3273 // force hiding of annotator toolbar
3274 if ( d->annotator )
3275 d->annotator->setEnabled( false );
3276 // force an update of the cursor
3277 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
3280 void PageView::slotSetMouseTextSelect()
3282 d->mouseMode = MouseTextSelect;
3283 // change the text in messageWindow (and show it if hidden)
3284 d->messageWindow->display( i18n( "Select text" ), PageViewMessage::Info, -1 );
3285 // force hiding of annotator toolbar
3286 if ( d->annotator )
3287 d->annotator->setEnabled( false );
3288 // force an update of the cursor
3289 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
3292 void PageView::slotToggleAnnotator( bool on )
3294 // the 'inHere' trick is needed as the slotSetMouseZoom() calls this
3295 static bool inHere = false;
3296 if ( inHere )
3297 return;
3298 inHere = true;
3300 // the annotator can be used in normal mouse mode only, so if asked for it,
3301 // switch to normal mode
3302 if ( on && d->mouseMode != MouseNormal )
3303 d->aMouseNormal->trigger();
3305 // create the annotator object if not present
3306 if ( !d->annotator )
3308 d->annotator = new PageViewAnnotator( this, d->document );
3309 bool allowTools = d->document->pages() > 0 && d->document->isAllowed( Okular::AllowNotes );
3310 d->annotator->setToolsEnabled( allowTools );
3311 d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() );
3314 // initialize/reset annotator (and show/hide toolbar)
3315 d->annotator->setEnabled( on );
3317 inHere = false;
3320 void PageView::slotScrollUp()
3322 if ( d->scrollIncrement < -9 )
3323 return;
3324 d->scrollIncrement--;
3325 slotAutoScoll();
3326 setFocus();
3329 void PageView::slotScrollDown()
3331 if ( d->scrollIncrement > 9 )
3332 return;
3333 d->scrollIncrement++;
3334 slotAutoScoll();
3335 setFocus();
3338 void PageView::slotRotateClockwise()
3340 int id = ( (int)d->document->rotation() + 1 ) % 4;
3341 d->document->setRotation( id );
3344 void PageView::slotRotateCounterClockwise()
3346 int id = ( (int)d->document->rotation() + 3 ) % 4;
3347 d->document->setRotation( id );
3350 void PageView::slotRotateOriginal()
3352 d->document->setRotation( 0 );
3355 void PageView::slotPageSizes( int newsize )
3357 if ( newsize < 0 || newsize >= d->document->pageSizes().count() )
3358 return;
3360 d->document->setPageSize( d->document->pageSizes().at( newsize ) );
3363 void PageView::slotTrimMarginsToggled( bool on )
3365 if ( Okular::Settings::trimMargins() != on )
3367 Okular::Settings::setTrimMargins( on );
3368 Okular::Settings::self()->writeConfig();
3369 if ( d->document->pages() > 0 )
3371 slotRelayoutPages();
3372 slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already!
3377 void PageView::slotToggleForms()
3379 toggleFormWidgets( !d->m_formsVisible );
3382 void PageView::slotFormWidgetChanged( FormWidgetIface *w )
3384 if ( !d->refreshTimer )
3386 d->refreshTimer = new QTimer( this );
3387 d->refreshTimer->setSingleShot( true );
3388 connect( d->refreshTimer, SIGNAL( timeout() ),
3389 this, SLOT( slotRefreshPage() ) );
3391 d->refreshPage = w->pageItem()->pageNumber();
3392 d->refreshTimer->start( 1000 );
3395 void PageView::slotRefreshPage()
3397 const int req = d->refreshPage;
3398 if ( req < 0 )
3399 return;
3400 d->refreshPage = -1;
3401 QMetaObject::invokeMethod( d->document, "refreshPixmaps", Qt::QueuedConnection,
3402 Q_ARG( int, req ) );
3405 void PageView::slotSpeakDocument()
3407 QString text;
3408 QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd();
3409 for ( ; it < itEnd; ++it )
3411 Okular::RegularAreaRect * area = textSelectionForItem( *it );
3412 text.append( (*it)->page()->text( area ) );
3413 text.append( '\n' );
3414 delete area;
3417 d->tts()->say( text );
3420 void PageView::slotSpeakCurrentPage()
3422 const int currentPage = d->document->viewport().pageNumber;
3424 PageViewItem *item = d->items.at( currentPage );
3425 Okular::RegularAreaRect * area = textSelectionForItem( item );
3426 const QString text = item->page()->text( area );
3427 delete area;
3429 d->tts()->say( text );
3432 void PageView::slotStopSpeaks()
3434 if ( !d->m_tts )
3435 return;
3437 d->m_tts->stopAllSpeechs();
3440 void PageView::slotAction( Okular::Action *action )
3442 d->document->processAction( action );
3444 //END private SLOTS
3446 #include "pageview.moc"
3448 /* kate: replace-tabs on; indent-width 4; */