compile
[kdegraphics.git] / okular / ui / pageview.cpp
blobb80b419061211414979f50355760b2d1bbfbe67b
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 <kstandardaction.h>
35 #include <kactioncollection.h>
36 #include <kmenu.h>
37 #include <klocale.h>
38 #include <kfiledialog.h>
39 #include <kglobal.h>
40 #include <kglobalsettings.h>
41 #include <kselectaction.h>
42 #include <ktoggleaction.h>
43 #include <kdebug.h>
44 #include <kmessagebox.h>
45 #include <kicon.h>
47 // system includes
48 #include <math.h>
49 #include <stdlib.h>
51 // local includes
52 #include "formwidgets.h"
53 #include "pageviewutils.h"
54 #include "pagepainter.h"
55 #include "core/annotations.h"
56 #include "annotwindow.h"
57 #include "guiutils.h"
58 #include "annotationpopup.h"
59 #include "pageviewannotator.h"
60 #include "toolaction.h"
61 #include "tts.h"
62 #include "videowidget.h"
63 #include "core/action.h"
64 #include "core/document.h"
65 #include "core/form.h"
66 #include "core/page.h"
67 #include "core/misc.h"
68 #include "core/generator.h"
69 #include "core/movie.h"
70 #include "settings.h"
72 static int pageflags = PagePainter::Accessibility | PagePainter::EnhanceLinks |
73 PagePainter::EnhanceImages | PagePainter::Highlights |
74 PagePainter::TextSelection | PagePainter::Annotations;
76 // structure used internally by PageView for data storage
77 class PageViewPrivate
79 public:
80 PageViewPrivate( PageView *qq );
82 FormWidgetsController* formWidgetsController();
83 OkularTTS* tts();
84 QString selectedText() const;
86 // the document, pageviewItems and the 'visible cache'
87 PageView *q;
88 Okular::Document * document;
89 QVector< PageViewItem * > items;
90 QLinkedList< PageViewItem * > visibleItems;
92 // view layout (columns and continuous in Settings), zoom and mouse
93 PageView::ZoomMode zoomMode;
94 float zoomFactor;
95 PageView::MouseMode mouseMode;
96 QPoint mouseGrabPos;
97 QPoint mousePressPos;
98 QPoint mouseSelectPos;
99 bool mouseMidZooming;
100 int mouseMidLastY;
101 bool mouseSelecting;
102 QRect mouseSelectionRect;
103 QColor mouseSelectionColor;
104 bool mouseTextSelecting;
105 QSet< int > pagesWithTextSelection;
106 bool mouseOnRect;
107 Okular::Annotation * mouseAnn;
108 QPoint mouseAnnPos;
110 // viewport move
111 bool viewportMoveActive;
112 QTime viewportMoveTime;
113 QPoint viewportMoveDest;
114 QTimer * viewportMoveTimer;
115 // auto scroll
116 int scrollIncrement;
117 QTimer * autoScrollTimer;
118 // annotations
119 PageViewAnnotator * annotator;
120 //text annotation dialogs list
121 QHash< Okular::Annotation *, AnnotWindow * > m_annowindows;
122 // other stuff
123 QTimer * delayResizeTimer;
124 bool dirtyLayout;
125 bool blockViewport; // prevents changes to viewport
126 bool blockPixmapsRequest; // prevent pixmap requests
127 PageViewMessage * messageWindow; // in pageviewutils.h
128 bool m_formsVisible;
129 FormWidgetsController *formsWidgetController;
130 OkularTTS * m_tts;
131 QTimer * refreshTimer;
132 int refreshPage;
134 // drag scroll
135 QPoint dragScrollVector;
136 QTimer dragScrollTimer;
138 // actions
139 KAction * aRotateClockwise;
140 KAction * aRotateCounterClockwise;
141 KAction * aRotateOriginal;
142 KSelectAction * aPageSizes;
143 KToggleAction * aTrimMargins;
144 QAction * aMouseNormal;
145 QAction * aMouseSelect;
146 QAction * aMouseTextSelect;
147 KToggleAction * aToggleAnnotator;
148 KSelectAction * aZoom;
149 QAction * aZoomIn;
150 QAction * aZoomOut;
151 KToggleAction * aZoomFitWidth;
152 KToggleAction * aZoomFitPage;
153 KToggleAction * aZoomFitText;
154 KSelectAction * aViewMode;
155 KToggleAction * aViewContinuous;
156 QAction * aPrevAction;
157 KAction * aToggleForms;
158 KAction * aSpeakDoc;
159 KAction * aSpeakPage;
160 KAction * aSpeakStop;
161 KActionCollection * actionCollection;
163 int setting_viewMode;
164 int setting_viewCols;
165 bool setting_centerFirst;
168 PageViewPrivate::PageViewPrivate( PageView *qq )
169 : q( qq )
173 FormWidgetsController* PageViewPrivate::formWidgetsController()
175 if ( !formsWidgetController )
177 formsWidgetController = new FormWidgetsController();
178 QObject::connect( formsWidgetController, SIGNAL( changed( FormWidgetIface* ) ),
179 q, SLOT( slotFormWidgetChanged( FormWidgetIface * ) ) );
180 QObject::connect( formsWidgetController, SIGNAL( action( Okular::Action* ) ),
181 q, SLOT( slotAction( Okular::Action* ) ) );
184 return formsWidgetController;
187 OkularTTS* PageViewPrivate::tts()
189 if ( !m_tts )
191 m_tts = new OkularTTS( q );
192 if ( aSpeakStop )
194 QObject::connect( m_tts, SIGNAL( hasSpeechs( bool ) ),
195 aSpeakStop, SLOT( setEnabled( bool ) ) );
196 QObject::connect( m_tts, SIGNAL( errorMessage( const QString & ) ),
197 q, SLOT( errorMessage( const QString & ) ) );
201 return m_tts;
205 class PageViewWidget : public QWidget
207 public:
208 PageViewWidget(PageView *pv) : QWidget(pv), m_pageView(pv) {}
210 protected:
211 bool event( QEvent *e )
213 if ( e->type() == QEvent::ToolTip && m_pageView->d->mouseMode == PageView::MouseNormal )
215 QHelpEvent * he = (QHelpEvent*)e;
216 PageViewItem * pageItem = m_pageView->pickItemOnPoint( he->x(), he->y() );
217 const Okular::ObjectRect * rect = 0;
218 const Okular::Action * link = 0;
219 const Okular::Annotation * ann = 0;
220 if ( pageItem )
222 double nX = pageItem->absToPageX( he->x() );
223 double nY = pageItem->absToPageY( he->y() );
224 rect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
225 if ( rect )
226 ann = static_cast< const Okular::AnnotationObjectRect * >( rect )->annotation();
227 else
229 rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
230 if ( rect )
231 link = static_cast< const Okular::Action * >( rect->object() );
235 if ( ann )
237 QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
238 r.translate( pageItem->uncroppedGeometry().topLeft() );
239 QString tip = GuiUtils::prettyToolTip( ann );
240 QToolTip::showText( he->globalPos(), tip, this, r );
242 else if ( link )
244 QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
245 r.translate( pageItem->uncroppedGeometry().topLeft() );
246 QString tip = link->actionTip();
247 if ( !tip.isEmpty() )
248 QToolTip::showText( he->globalPos(), tip, this, r );
250 e->accept();
251 return true;
253 else
254 // do not stop the event
255 return QWidget::event( e );
258 // viewport events
259 void paintEvent( QPaintEvent *e )
261 m_pageView->contentsPaintEvent(e);
264 void mouseMoveEvent( QMouseEvent *e )
266 m_pageView->contentsMouseMoveEvent(e);
269 void mousePressEvent( QMouseEvent *e )
271 m_pageView->contentsMousePressEvent(e);
274 void mouseReleaseEvent( QMouseEvent *e )
276 m_pageView->contentsMouseReleaseEvent(e);
280 private:
281 PageView *m_pageView;
284 /* PageView. What's in this file? -> quick overview.
285 * Code weight (in rows) and meaning:
286 * 160 - constructor and creating actions plus their connected slots (empty stuff)
287 * 70 - DocumentObserver inherited methodes (important)
288 * 550 - events: mouse, keyboard, drag/drop
289 * 170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes
290 * 100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc..
291 * other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable,
292 * and many insignificant stuff like this comment :-)
294 PageView::PageView( QWidget *parent, Okular::Document *document )
295 : QScrollArea( parent )
296 , Okular::View( QString::fromLatin1( "PageView" ) )
298 // create and initialize private storage structure
299 d = new PageViewPrivate( this );
300 d->document = document;
301 d->aRotateClockwise = 0;
302 d->aRotateCounterClockwise = 0;
303 d->aRotateOriginal = 0;
304 d->aViewMode = 0;
305 d->zoomMode = PageView::ZoomFitWidth;
306 d->zoomFactor = 1.0;
307 d->mouseMode = MouseNormal;
308 d->mouseMidZooming = false;
309 d->mouseSelecting = false;
310 d->mouseTextSelecting = false;
311 d->mouseOnRect = false;
312 d->mouseAnn = 0;
313 d->viewportMoveActive = false;
314 d->viewportMoveTimer = 0;
315 d->scrollIncrement = 0;
316 d->autoScrollTimer = 0;
317 d->annotator = 0;
318 d->delayResizeTimer = 0;
319 d->dirtyLayout = false;
320 d->blockViewport = false;
321 d->blockPixmapsRequest = false;
322 d->messageWindow = new PageViewMessage(this);
323 d->m_formsVisible = false;
324 d->formsWidgetController = 0;
325 d->m_tts = 0;
326 d->refreshTimer = 0;
327 d->refreshPage = -1;
328 d->aRotateClockwise = 0;
329 d->aRotateCounterClockwise = 0;
330 d->aRotateOriginal = 0;
331 d->aPageSizes = 0;
332 d->aTrimMargins = 0;
333 d->aMouseNormal = 0;
334 d->aMouseSelect = 0;
335 d->aMouseTextSelect = 0;
336 d->aToggleAnnotator = 0;
337 d->aZoomFitWidth = 0;
338 d->aZoomFitPage = 0;
339 d->aZoomFitText = 0;
340 d->aViewMode = 0;
341 d->aViewContinuous = 0;
342 d->aPrevAction = 0;
343 d->aToggleForms = 0;
344 d->aSpeakDoc = 0;
345 d->aSpeakPage = 0;
346 d->aSpeakStop = 0;
347 d->actionCollection = 0;
348 d->aPageSizes=0;
349 d->setting_viewMode = Okular::Settings::viewMode();
350 d->setting_viewCols = Okular::Settings::viewColumns();
351 d->setting_centerFirst = Okular::Settings::centerFirstPageInRow();
353 setFrameStyle(QFrame::NoFrame);
355 setAttribute( Qt::WA_StaticContents );
357 setObjectName( QLatin1String( "okular::pageView" ) );
359 // widget setup: setup focus, accept drops and track mouse
360 setWidget(new PageViewWidget(this));
361 viewport()->setFocusProxy( this );
362 viewport()->setFocusPolicy( Qt::StrongFocus );
363 widget()->setAttribute( Qt::WA_OpaquePaintEvent );
364 widget()->setAttribute( Qt::WA_NoSystemBackground );
365 setAcceptDrops( true );
366 widget()->setMouseTracking( true );
367 setWidgetResizable(true);
369 // conntect the padding of the viewport to pixmaps requests
370 connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRequestVisiblePixmaps(int)));
371 connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRequestVisiblePixmaps(int)));
372 connect( &d->dragScrollTimer, SIGNAL(timeout()), this, SLOT(slotDragScroll()) );
374 // set a corner button to resize the view to the page size
375 // QPushButton * resizeButton = new QPushButton( viewport() );
376 // resizeButton->setPixmap( SmallIcon("crop") );
377 // setCornerWidget( resizeButton );
378 // resizeButton->setEnabled( false );
379 // connect(...);
380 setAttribute( Qt::WA_InputMethodEnabled, true );
382 // schedule the welcome message
383 QMetaObject::invokeMethod(this, "slotShowWelcome", Qt::QueuedConnection);
386 PageView::~PageView()
388 if ( d->m_tts )
389 d->m_tts->stopAllSpeechs();
391 // delete the local storage structure
392 qDeleteAll(d->m_annowindows);
393 // delete all widgets
394 QVector< PageViewItem * >::const_iterator dIt = d->items.begin(), dEnd = d->items.end();
395 for ( ; dIt != dEnd; ++dIt )
396 delete *dIt;
397 delete d->formsWidgetController;
398 d->document->removeObserver( this );
399 delete d;
402 void PageView::setupBaseActions( KActionCollection * ac )
404 d->actionCollection = ac;
406 // Zoom actions ( higher scales takes lots of memory! )
407 d->aZoom = new KSelectAction(KIcon( "zoom-original" ), i18n("Zoom"), this);
408 ac->addAction("zoom_to", d->aZoom );
409 d->aZoom->setEditable( true );
410 d->aZoom->setMaxComboViewCount( 13 );
411 connect( d->aZoom, SIGNAL( triggered(QAction *) ), this, SLOT( slotZoom() ) );
412 updateZoomText();
414 d->aZoomIn = KStandardAction::zoomIn( this, SLOT( slotZoomIn() ), ac );
416 d->aZoomOut = KStandardAction::zoomOut( this, SLOT( slotZoomOut() ), ac );
419 void PageView::setupActions( KActionCollection * ac )
421 d->actionCollection = ac;
423 // orientation menu actions
424 d->aRotateClockwise = new KAction( KIcon( "object-rotate-right" ), i18n( "Rotate Right" ), this );
425 d->aRotateClockwise->setIconText( i18nc( "Rotate right", "Right" ) );
426 ac->addAction( "view_orientation_rotate_cw", d->aRotateClockwise );
427 d->aRotateClockwise->setEnabled( false );
428 connect( d->aRotateClockwise, SIGNAL( triggered() ), this, SLOT( slotRotateClockwise() ) );
429 d->aRotateCounterClockwise = new KAction( KIcon( "object-rotate-left" ), i18n( "Rotate Left" ), this );
430 d->aRotateCounterClockwise->setIconText( i18nc( "Rotate left", "Left" ) );
431 ac->addAction( "view_orientation_rotate_ccw", d->aRotateCounterClockwise );
432 d->aRotateCounterClockwise->setEnabled( false );
433 connect( d->aRotateCounterClockwise, SIGNAL( triggered() ), this, SLOT( slotRotateCounterClockwise() ) );
434 d->aRotateOriginal = new KAction( i18n( "Original Orientation" ), this );
435 ac->addAction( "view_orientation_original", d->aRotateOriginal );
436 d->aRotateOriginal->setEnabled( false );
437 connect( d->aRotateOriginal, SIGNAL( triggered() ), this, SLOT( slotRotateOriginal() ) );
439 d->aPageSizes = new KSelectAction(i18n("&Page Size"), this);
440 ac->addAction("view_pagesizes", d->aPageSizes);
441 d->aPageSizes->setEnabled( false );
443 connect( d->aPageSizes , SIGNAL( triggered( int ) ),
444 this, SLOT( slotPageSizes( int ) ) );
446 d->aTrimMargins = new KToggleAction( i18n( "&Trim Margins" ), this );
447 ac->addAction("view_trim_margins", d->aTrimMargins );
448 connect( d->aTrimMargins, SIGNAL( toggled( bool ) ), SLOT( slotTrimMarginsToggled( bool ) ) );
449 d->aTrimMargins->setChecked( Okular::Settings::trimMargins() );
451 d->aZoomFitWidth = new KToggleAction(KIcon( "zoom-fit-width" ), i18n("Fit &Width"), this);
452 ac->addAction("view_fit_to_width", d->aZoomFitWidth );
453 connect( d->aZoomFitWidth, SIGNAL( toggled( bool ) ), SLOT( slotFitToWidthToggled( bool ) ) );
455 d->aZoomFitPage = new KToggleAction(KIcon( "zoom-fit-best" ), i18n("Fit &Page"), this);
456 ac->addAction("view_fit_to_page", d->aZoomFitPage );
457 connect( d->aZoomFitPage, SIGNAL( toggled( bool ) ), SLOT( slotFitToPageToggled( bool ) ) );
460 d->aZoomFitText = new KToggleAction(KIcon( "zoom-fit-best" ), i18n("Fit &Text"), this);
461 ac->addAction("zoom_fit_text", d->aZoomFitText );
462 connect( d->aZoomFitText, SIGNAL( toggled( bool ) ), SLOT( slotFitToTextToggled( bool ) ) );
465 // View-Layout actions
466 QStringList viewModes;
467 viewModes.append( i18n( "Single Page" ) );
468 viewModes.append( i18n( "Facing Pages" ) );
469 viewModes.append( i18n( "Overview" ) );
471 d->aViewMode = new KSelectAction(KIcon( "view-split-left-right" ), i18n("&View Mode"), this);
472 ac->addAction("view_render_mode", d->aViewMode );
473 connect( d->aViewMode, SIGNAL( triggered( int ) ), SLOT( slotViewMode( int ) ) );
474 d->aViewMode->setItems( viewModes );
475 d->aViewMode->setCurrentItem( Okular::Settings::viewMode() );
477 d->aViewContinuous = new KToggleAction(KIcon( "view-list-text" ), i18n("&Continuous"), this);
478 ac->addAction("view_continuous", d->aViewContinuous );
479 connect( d->aViewContinuous, SIGNAL( toggled( bool ) ), SLOT( slotContinuousToggled( bool ) ) );
480 d->aViewContinuous->setChecked( Okular::Settings::viewContinuous() );
482 // Mouse-Mode actions
483 QActionGroup * actGroup = new QActionGroup( this );
484 actGroup->setExclusive( true );
485 d->aMouseNormal = new KAction( KIcon( "input-mouse" ), i18n( "&Browse Tool" ), this );
486 ac->addAction("mouse_drag", d->aMouseNormal );
487 connect( d->aMouseNormal, SIGNAL( triggered() ), this, SLOT( slotSetMouseNormal() ) );
488 d->aMouseNormal->setIconText( i18nc( "Browse Tool", "Browse" ) );
489 d->aMouseNormal->setCheckable( true );
490 d->aMouseNormal->setShortcut( Qt::CTRL + Qt::Key_1 );
491 d->aMouseNormal->setActionGroup( actGroup );
492 d->aMouseNormal->setChecked( true );
494 KAction * mz = new KAction(KIcon( "zoom-original" ), i18n("&Zoom Tool"), this);
495 ac->addAction("mouse_zoom", mz );
496 connect( mz, SIGNAL( triggered() ), this, SLOT( slotSetMouseZoom() ) );
497 mz->setIconText( i18nc( "Zoom Tool", "Zoom" ) );
498 mz->setCheckable( true );
499 mz->setShortcut( Qt::CTRL + Qt::Key_2 );
500 mz->setActionGroup( actGroup );
502 d->aMouseSelect = new KAction(KIcon( "select-rectangular" ), i18n("&Selection Tool"), this);
503 ac->addAction("mouse_select", d->aMouseSelect );
504 connect( d->aMouseSelect, SIGNAL( triggered() ), this, SLOT( slotSetMouseSelect() ) );
505 d->aMouseSelect->setIconText( i18nc( "Select Tool", "Selection" ) );
506 d->aMouseSelect->setCheckable( true );
507 d->aMouseSelect->setShortcut( Qt::CTRL + Qt::Key_3 );
508 d->aMouseSelect->setActionGroup( actGroup );
510 d->aMouseTextSelect = new KAction(KIcon( "draw-text" ), i18n("&Text Selection Tool"), this);
511 ac->addAction("mouse_textselect", d->aMouseTextSelect );
512 connect( d->aMouseTextSelect, SIGNAL( triggered() ), this, SLOT( slotSetMouseTextSelect() ) );
513 d->aMouseTextSelect->setIconText( i18nc( "Text Selection Tool", "Text Selection" ) );
514 d->aMouseTextSelect->setCheckable( true );
515 d->aMouseTextSelect->setShortcut( Qt::CTRL + Qt::Key_4 );
516 d->aMouseTextSelect->setActionGroup( actGroup );
518 d->aToggleAnnotator = new KToggleAction(KIcon( "draw-freehand" ), i18n("&Review"), this);
519 ac->addAction("mouse_toggle_annotate", d->aToggleAnnotator );
520 d->aToggleAnnotator->setCheckable( true );
521 connect( d->aToggleAnnotator, SIGNAL( toggled( bool ) ), SLOT( slotToggleAnnotator( bool ) ) );
522 d->aToggleAnnotator->setShortcut( Qt::Key_F6 );
524 ToolAction *ta = new ToolAction( this );
525 ac->addAction( "mouse_selecttools", ta );
526 ta->addAction( d->aMouseSelect );
527 ta->addAction( d->aMouseTextSelect );
529 // speak actions
530 d->aSpeakDoc = new KAction( KIcon( "text-speak" ), i18n( "Speak Whole Document" ), this );
531 ac->addAction( "speak_document", d->aSpeakDoc );
532 d->aSpeakDoc->setEnabled( false );
533 connect( d->aSpeakDoc, SIGNAL( triggered() ), SLOT( slotSpeakDocument() ) );
535 d->aSpeakPage = new KAction( KIcon( "text-speak" ), i18n( "Speak Current Page" ), this );
536 ac->addAction( "speak_current_page", d->aSpeakPage );
537 d->aSpeakPage->setEnabled( false );
538 connect( d->aSpeakPage, SIGNAL( triggered() ), SLOT( slotSpeakCurrentPage() ) );
540 d->aSpeakStop = new KAction( KIcon( "media-playback-stop" ), i18n( "Stop Speaking" ), this );
541 ac->addAction( "speak_stop_all", d->aSpeakStop );
542 d->aSpeakStop->setEnabled( false );
543 connect( d->aSpeakStop, SIGNAL( triggered() ), SLOT( slotStopSpeaks() ) );
545 // Other actions
546 KAction * su = new KAction(i18n("Scroll Up"), this);
547 ac->addAction("view_scroll_up", su );
548 connect( su, SIGNAL( triggered() ), this, SLOT( slotScrollUp() ) );
549 su->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_Up) );
550 addAction(su);
552 KAction * sd = new KAction(i18n("Scroll Down"), this);
553 ac->addAction("view_scroll_down", sd );
554 connect( sd, SIGNAL( triggered() ), this, SLOT( slotScrollDown() ) );
555 sd->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_Down) );
556 addAction(sd);
558 d->aToggleForms = new KAction( this );
559 ac->addAction( "view_toggle_forms", d->aToggleForms );
560 connect( d->aToggleForms, SIGNAL( triggered() ), this, SLOT( slotToggleForms() ) );
561 d->aToggleForms->setEnabled( false );
562 toggleFormWidgets( false );
565 bool PageView::canFitPageWidth() const
567 return Okular::Settings::viewMode() != 0 || d->zoomMode != ZoomFitWidth;
570 void PageView::fitPageWidth( int page )
572 // zoom: Fit Width, columns: 1. setActions + relayout + setPage + update
573 d->zoomMode = ZoomFitWidth;
574 Okular::Settings::setViewMode( 0 );
575 d->aZoomFitWidth->setChecked( true );
576 d->aZoomFitPage->setChecked( false );
577 // d->aZoomFitText->setChecked( false );
578 d->aViewMode->setCurrentItem( 0 );
579 viewport()->setUpdatesEnabled( false );
580 slotRelayoutPages();
581 viewport()->setUpdatesEnabled( true );
582 d->document->setViewportPage( page );
583 updateZoomText();
584 setFocus();
587 void PageView::setAnnotationWindow( Okular::Annotation * annotation )
589 if ( !annotation )
590 return;
592 // find the annot window
593 AnnotWindow* existWindow = 0;
594 QHash< Okular::Annotation *, AnnotWindow * >::ConstIterator it = d->m_annowindows.find( annotation );
595 if ( it != d->m_annowindows.end() )
597 existWindow = *it;
600 if ( existWindow == 0 )
602 existWindow = new AnnotWindow( this, annotation );
604 d->m_annowindows.insert( annotation, existWindow );
607 existWindow->show();
610 void PageView::removeAnnotationWindow( Okular::Annotation *annotation )
612 QHash< Okular::Annotation *, AnnotWindow * >::Iterator it = d->m_annowindows.find( annotation );
613 if ( it != d->m_annowindows.end() )
615 delete *it;
616 d->m_annowindows.erase( it );
620 void PageView::displayMessage( const QString & message,PageViewMessage::Icon icon,int duration )
622 if ( !Okular::Settings::showOSD() )
624 if (icon == PageViewMessage::Error)
625 KMessageBox::error( this, message );
626 else
627 return;
630 // hide messageWindow if string is empty
631 if ( message.isEmpty() )
632 return d->messageWindow->hide();
634 // display message (duration is length dependant)
635 if (duration==-1)
636 duration = 500 + 100 * message.length();
637 d->messageWindow->display( message, icon, duration );
640 void PageView::reparseConfig()
642 // set the scroll bars policies
643 Qt::ScrollBarPolicy scrollBarMode = Okular::Settings::showScrollBars() ?
644 Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff;
645 if ( horizontalScrollBarPolicy() != scrollBarMode )
647 setHorizontalScrollBarPolicy( scrollBarMode );
648 setVerticalScrollBarPolicy( scrollBarMode );
651 const int viewMode = Okular::Settings::viewMode();
652 if ( ( viewMode == 2 && ( (int)Okular::Settings::viewColumns() != d->setting_viewCols ) )
653 || ( viewMode > 0 && ( Okular::Settings::centerFirstPageInRow() != d->setting_centerFirst ) )
656 d->setting_viewMode = Okular::Settings::viewMode();
657 d->setting_viewCols = Okular::Settings::viewColumns();
658 d->setting_centerFirst = Okular::Settings::centerFirstPageInRow();
660 slotRelayoutPages();
664 KAction *PageView::toggleFormsAction() const
666 return d->aToggleForms;
669 QString PageViewPrivate::selectedText() const
671 if ( pagesWithTextSelection.isEmpty() )
672 return QString();
674 QString text;
675 QList< int > selpages = pagesWithTextSelection.toList();
676 qSort( selpages );
677 const Okular::Page * pg = 0;
678 if ( selpages.count() == 1 )
680 pg = document->page( selpages.first() );
681 text.append( pg->text( pg->textSelection() ) );
683 else
685 pg = document->page( selpages.first() );
686 text.append( pg->text( pg->textSelection() ) );
687 int end = selpages.count() - 1;
688 for( int i = 1; i < end; ++i )
690 pg = document->page( selpages.at( i ) );
691 text.append( pg->text() );
693 pg = document->page( selpages.last() );
694 text.append( pg->text( pg->textSelection() ) );
696 return text;
699 void PageView::copyTextSelection() const
701 const QString text = d->selectedText();
702 if ( !text.isEmpty() )
704 QClipboard *cb = QApplication::clipboard();
705 cb->setText( text, QClipboard::Clipboard );
709 void PageView::selectAll()
711 if ( d->mouseMode != MouseTextSelect )
712 return;
714 QVector< PageViewItem * >::const_iterator it = d->items.begin(), itEnd = d->items.end();
715 for ( ; it < itEnd; ++it )
717 Okular::RegularAreaRect * area = textSelectionForItem( *it );
718 d->pagesWithTextSelection.insert( (*it)->pageNumber() );
719 d->document->setPageTextSelection( (*it)->pageNumber(), area, palette().color( QPalette::Active, QPalette::Highlight ) );
723 //BEGIN DocumentObserver inherited methods
724 void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags )
726 bool documentChanged = setupFlags & Okular::DocumentObserver::DocumentChanged;
727 // reuse current pages if nothing new
728 if ( ( pageSet.count() == d->items.count() ) && !documentChanged && !( setupFlags & Okular::DocumentObserver::NewLayoutForPages ) )
730 int count = pageSet.count();
731 for ( int i = 0; (i < count) && !documentChanged; i++ )
732 if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() )
733 documentChanged = true;
734 if ( !documentChanged )
735 return;
738 // delete all widgets (one for each page in pageSet)
739 QVector< PageViewItem * >::const_iterator dIt = d->items.begin(), dEnd = d->items.end();
740 for ( ; dIt != dEnd; ++dIt )
741 delete *dIt;
742 d->items.clear();
743 d->visibleItems.clear();
744 d->pagesWithTextSelection.clear();
745 toggleFormWidgets( false );
746 if ( d->formsWidgetController )
747 d->formsWidgetController->dropRadioButtons();
749 bool haspages = !pageSet.isEmpty();
750 bool hasformwidgets = false;
751 // create children widgets
752 QVector< Okular::Page * >::const_iterator setIt = pageSet.begin(), setEnd = pageSet.end();
753 for ( ; setIt != setEnd; ++setIt )
755 PageViewItem * item = new PageViewItem( *setIt );
756 d->items.push_back( item );
757 #ifdef PAGEVIEW_DEBUG
758 kDebug().nospace() << "cropped geom for " << d->items.last()->pageNumber() << " is " << d->items.last()->croppedGeometry();
759 #endif
760 const QLinkedList< Okular::FormField * > pageFields = (*setIt)->formFields();
761 QLinkedList< Okular::FormField * >::const_iterator ffIt = pageFields.begin(), ffEnd = pageFields.end();
762 for ( ; ffIt != ffEnd; ++ffIt )
764 Okular::FormField * ff = *ffIt;
765 FormWidgetIface * w = FormWidgetFactory::createWidget( ff, widget() );
766 if ( w )
768 w->setPageItem( item );
769 w->setFormWidgetsController( d->formWidgetsController() );
770 w->setVisibility( false );
771 w->setCanBeFilled( d->document->isAllowed( Okular::AllowFillForms ) );
772 item->formWidgets().insert( ff->id(), w );
773 hasformwidgets = true;
776 const QLinkedList< Okular::Annotation * > annotations = (*setIt)->annotations();
777 QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.begin(), aEnd = annotations.end();
778 for ( ; aIt != aEnd; ++aIt )
780 Okular::Annotation * a = *aIt;
781 if ( a->subType() == Okular::Annotation::AMovie )
783 Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a );
784 VideoWidget * vw = new VideoWidget( movieAnn, d->document, widget() );
785 item->videoWidgets().insert( movieAnn->movie(), vw );
786 vw->show();
791 // invalidate layout so relayout/repaint will happen on next viewport change
792 if ( haspages )
793 // TODO for Enrico: Check if doing always the slotRelayoutPages() is not
794 // suboptimal in some cases, i'd say it is not but a recheck will not hurt
795 // Need slotRelayoutPages() here instead of d->dirtyLayout = true
796 // because opening a document from another document will not trigger a viewportchange
797 // so pages are never relayouted
798 QMetaObject::invokeMethod(this, "slotRelayoutPages", Qt::QueuedConnection);
799 else
801 // update the mouse cursor when closing because we may have close through a link and
802 // want the cursor to come back to the normal cursor
803 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
804 setWidgetResizable(true);
807 // OSD to display pages
808 if ( documentChanged && pageSet.count() > 0 && Okular::Settings::showOSD() )
809 d->messageWindow->display(
810 i18np(" Loaded a one-page document.",
811 " Loaded a %1-page document.",
812 pageSet.count() ),
813 PageViewMessage::Info, 4000 );
815 if ( d->aPageSizes )
816 { // may be null if dummy mode is on
817 bool pageSizes = d->document->supportsPageSizes();
818 d->aPageSizes->setEnabled( pageSizes );
819 // set the new page sizes:
820 // - if the generator supports them
821 // - if the document changed
822 if ( pageSizes && documentChanged )
824 QStringList items;
825 foreach ( const Okular::PageSize &p, d->document->pageSizes() )
826 items.append( p.name() );
827 d->aPageSizes->setItems( items );
830 if ( d->aRotateClockwise )
831 d->aRotateClockwise->setEnabled( haspages );
832 if ( d->aRotateCounterClockwise )
833 d->aRotateCounterClockwise->setEnabled( haspages );
834 if ( d->aRotateOriginal )
835 d->aRotateOriginal->setEnabled( haspages );
836 if ( d->aToggleForms )
837 { // may be null if dummy mode is on
838 d->aToggleForms->setEnabled( haspages && hasformwidgets );
840 bool allowAnnotations = d->document->isAllowed( Okular::AllowNotes );
841 if ( d->annotator )
843 bool allowTools = haspages && allowAnnotations;
844 d->annotator->setToolsEnabled( allowTools );
845 d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() );
847 if ( d->aToggleAnnotator )
849 if ( !allowAnnotations && d->aToggleAnnotator->isChecked() )
851 d->aToggleAnnotator->trigger();
853 d->aToggleAnnotator->setEnabled( allowAnnotations );
855 if ( d->aSpeakDoc )
857 const bool enablettsactions = haspages ? Okular::Settings::useKTTSD() : false;
858 d->aSpeakDoc->setEnabled( enablettsactions );
859 d->aSpeakPage->setEnabled( enablettsactions );
863 void PageView::notifyViewportChanged( bool smoothMove )
865 // if we are the one changing viewport, skip this nofity
866 if ( d->blockViewport )
867 return;
869 // block setViewport outgoing calls
870 d->blockViewport = true;
872 // find PageViewItem matching the viewport description
873 const Okular::DocumentViewport & vp = d->document->viewport();
874 PageViewItem * item = 0;
875 QVector< PageViewItem * >::const_iterator iIt = d->items.begin(), iEnd = d->items.end();
876 for ( ; iIt != iEnd; ++iIt )
877 if ( (*iIt)->pageNumber() == vp.pageNumber )
879 item = *iIt;
880 break;
882 if ( !item )
884 kWarning() << "viewport for page" << vp.pageNumber << "has no matching item!";
885 d->blockViewport = false;
886 return;
888 #ifdef PAGEVIEW_DEBUG
889 kDebug() << "document viewport changed";
890 #endif
891 // relayout in "Single Pages" mode or if a relayout is pending
892 d->blockPixmapsRequest = true;
893 if ( !Okular::Settings::viewContinuous() || d->dirtyLayout )
894 slotRelayoutPages();
896 // restore viewport center or use default {x-center,v-top} alignment
897 const QRect & r = item->croppedGeometry();
898 int newCenterX = r.left(),
899 newCenterY = r.top();
900 if ( vp.rePos.enabled )
902 if ( vp.rePos.pos == Okular::DocumentViewport::Center )
904 newCenterX += (int)( vp.rePos.normalizedX * (double)r.width() );
905 newCenterY += (int)( vp.rePos.normalizedY * (double)r.height() );
907 else
909 // TopLeft
910 newCenterX += (int)( vp.rePos.normalizedX * (double)r.width() + viewport()->width() / 2 );
911 newCenterY += (int)( vp.rePos.normalizedY * (double)r.height() + viewport()->height() / 2 );
914 else
916 newCenterX += r.width() / 2;
917 newCenterY += viewport()->height() / 2 - 10;
920 // if smooth movement requested, setup parameters and start it
921 if ( smoothMove )
923 d->viewportMoveActive = true;
924 d->viewportMoveTime.start();
925 d->viewportMoveDest.setX( newCenterX );
926 d->viewportMoveDest.setY( newCenterY );
927 if ( !d->viewportMoveTimer )
929 d->viewportMoveTimer = new QTimer( this );
930 connect( d->viewportMoveTimer, SIGNAL( timeout() ),
931 this, SLOT( slotMoveViewport() ) );
933 d->viewportMoveTimer->start( 25 );
934 verticalScrollBar()->setEnabled( false );
935 horizontalScrollBar()->setEnabled( false );
937 else
938 center( newCenterX, newCenterY );
939 d->blockPixmapsRequest = false;
941 // request visible pixmaps in the current viewport and recompute it
942 slotRequestVisiblePixmaps();
944 // enable setViewport calls
945 d->blockViewport = false;
947 // update zoom text if in a ZoomFit/* zoom mode
948 if ( d->zoomMode != ZoomFixed )
949 updateZoomText();
951 // since the page has moved below cursor, update it
952 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
955 void PageView::notifyPageChanged( int pageNumber, int changedFlags )
957 // only handle pixmap / highlight changes notifies
958 if ( changedFlags & DocumentObserver::Bookmark )
959 return;
961 if ( changedFlags & DocumentObserver::Annotations )
963 const QLinkedList< Okular::Annotation * > annots = d->document->page( pageNumber )->annotations();
964 const QLinkedList< Okular::Annotation * >::ConstIterator annItEnd = annots.end();
965 QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin();
966 for ( ; it != d->m_annowindows.end(); )
968 QLinkedList< Okular::Annotation * >::ConstIterator annIt = qFind( annots, it.key() );
969 if ( annIt != annItEnd )
971 (*it)->reloadInfo();
972 ++it;
974 else
976 delete *it;
977 it = d->m_annowindows.erase( it );
982 if ( changedFlags & DocumentObserver::BoundingBox )
984 #ifdef PAGEVIEW_DEBUG
985 kDebug() << "BoundingBox change on page" << pageNumber;
986 #endif
987 slotRelayoutPages();
988 slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already!
989 // Repaint the whole widget since layout may have changed
990 widget()->update();
991 return;
994 // iterate over visible items: if page(pageNumber) is one of them, repaint it
995 QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.begin(), iEnd = d->visibleItems.end();
996 for ( ; iIt != iEnd; ++iIt )
997 if ( (*iIt)->pageNumber() == pageNumber && (*iIt)->isVisible() )
999 // update item's rectangle plus the little outline
1000 QRect expandedRect = (*iIt)->croppedGeometry();
1001 expandedRect.adjust( -1, -1, 3, 3 );
1002 widget()->update( expandedRect );
1004 // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor
1005 if ( cursor().shape() != Qt::SizeVerCursor )
1007 // since the page has been regenerated below cursor, update it
1008 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
1010 break;
1014 void PageView::notifyContentsCleared( int changedFlags )
1016 // if pixmaps were cleared, re-ask them
1017 if ( changedFlags & DocumentObserver::Pixmap )
1018 QMetaObject::invokeMethod(this, "slotRequestVisiblePixmaps", Qt::QueuedConnection);
1021 void PageView::notifyZoom( int factor )
1023 if ( factor > 0 )
1024 updateZoom( ZoomIn );
1025 else
1026 updateZoom( ZoomOut );
1029 bool PageView::canUnloadPixmap( int pageNumber ) const
1031 if ( Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Aggressive )
1033 // if the item is visible, forbid unloading
1034 QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.begin(), vEnd = d->visibleItems.end();
1035 for ( ; vIt != vEnd; ++vIt )
1036 if ( (*vIt)->pageNumber() == pageNumber )
1037 return false;
1039 else
1041 // forbid unloading of the visible items, and of the previous and next
1042 QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.begin(), vEnd = d->visibleItems.end();
1043 for ( ; vIt != vEnd; ++vIt )
1044 if ( abs( (*vIt)->pageNumber() - pageNumber ) <= 1 )
1045 return false;
1047 // if hidden premit unloading
1048 return true;
1050 //END DocumentObserver inherited methods
1052 //BEGIN View inherited methods
1053 bool PageView::supportsCapability( ViewCapability capability ) const
1055 switch ( capability )
1057 case Zoom:
1058 case ZoomModality:
1059 return true;
1061 return false;
1064 Okular::View::CapabilityFlags PageView::capabilityFlags( ViewCapability capability ) const
1066 switch ( capability )
1068 case Zoom:
1069 case ZoomModality:
1070 return CapabilityRead | CapabilityWrite | CapabilitySerializable;
1072 return 0;
1075 QVariant PageView::capability( ViewCapability capability ) const
1077 switch ( capability )
1079 case Zoom:
1080 return d->zoomFactor;
1081 case ZoomModality:
1082 return d->zoomMode;
1084 return QVariant();
1087 void PageView::setCapability( ViewCapability capability, const QVariant &option )
1089 switch ( capability )
1091 case Zoom:
1093 bool ok = true;
1094 double factor = option.toDouble( &ok );
1095 if ( ok && factor > 0.0 )
1097 d->zoomFactor = static_cast< float >( factor );
1098 updateZoom( ZoomRefreshCurrent );
1100 break;
1102 case ZoomModality:
1104 bool ok = true;
1105 int mode = option.toInt( &ok );
1106 if ( ok )
1108 if ( mode >= 0 && mode < 3 )
1109 updateZoom( (ZoomMode)mode );
1111 break;
1116 //END View inherited methods
1118 //BEGIN widget events
1119 void PageView::contentsPaintEvent(QPaintEvent *pe)
1121 // create the rect into contents from the clipped screen rect
1122 QRect viewportRect = viewport()->rect();
1123 viewportRect.translate( horizontalScrollBar()->value(), verticalScrollBar()->value() );
1124 QRect contentsRect = pe->rect().intersect( viewportRect );
1125 if ( !contentsRect.isValid() )
1126 return;
1128 #ifdef PAGEVIEW_DEBUG
1129 kDebug() << "paintevent" << contentsRect;
1130 #endif
1132 // create the screen painter. a pixel painted at contentsX,contentsY
1133 // appears to the top-left corner of the scrollview.
1134 QPainter screenPainter( widget() );
1136 // selectionRect is the normalized mouse selection rect
1137 QRect selectionRect = d->mouseSelectionRect;
1138 if ( !selectionRect.isNull() )
1139 selectionRect = selectionRect.normalized();
1140 // selectionRectInternal without the border
1141 QRect selectionRectInternal = selectionRect;
1142 selectionRectInternal.adjust( 1, 1, -1, -1 );
1143 // color for blending
1144 QColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ?
1145 d->mouseSelectionColor : Qt::red;
1147 // subdivide region into rects
1148 const QVector<QRect> &allRects = pe->region().rects();
1149 uint numRects = allRects.count();
1151 // preprocess rects area to see if it worths or not using subdivision
1152 uint summedArea = 0;
1153 for ( uint i = 0; i < numRects; i++ )
1155 const QRect & r = allRects[i];
1156 summedArea += r.width() * r.height();
1158 // very elementary check: SUMj(Region[j].area) is less than boundingRect.area
1159 bool useSubdivision = summedArea < (0.6 * contentsRect.width() * contentsRect.height());
1160 if ( !useSubdivision )
1161 numRects = 1;
1163 // iterate over the rects (only one loop if not using subdivision)
1164 for ( uint i = 0; i < numRects; i++ )
1166 if ( useSubdivision )
1168 // set 'contentsRect' to a part of the sub-divided region
1169 contentsRect = allRects[i].normalized().intersect( viewportRect );
1170 if ( !contentsRect.isValid() )
1171 continue;
1173 #ifdef PAGEVIEW_DEBUG
1174 kDebug() << contentsRect;
1175 #endif
1177 // note: this check will take care of all things requiring alpha blending (not only selection)
1178 bool wantCompositing = !selectionRect.isNull() && contentsRect.intersects( selectionRect );
1180 if ( wantCompositing && Okular::Settings::enableCompositing() )
1182 // create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0})
1183 QPixmap doubleBuffer( contentsRect.size() );
1184 QPainter pixmapPainter( &doubleBuffer );
1185 pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() );
1187 // 1) Layer 0: paint items and clear bg on unpainted rects
1188 drawDocumentOnPainter( contentsRect, &pixmapPainter );
1189 // 2) Layer 1a: paint (blend) transparent selection
1190 if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
1191 !selectionRectInternal.contains( contentsRect ) )
1193 QRect blendRect = selectionRectInternal.intersect( contentsRect );
1194 // skip rectangles covered by the selection's border
1195 if ( blendRect.isValid() )
1197 // grab current pixmap into a new one to colorize contents
1198 QPixmap blendedPixmap( blendRect.width(), blendRect.height() );
1199 QPainter p( &blendedPixmap );
1200 p.drawPixmap( 0, 0, doubleBuffer,
1201 blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(),
1202 blendRect.width(), blendRect.height() );
1204 QColor blCol = selBlendColor.dark( 140 );
1205 blCol.setAlphaF( 0.2 );
1206 p.fillRect( blendedPixmap.rect(), blCol );
1207 p.end();
1208 // copy the blended pixmap back to its place
1209 pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap );
1211 // draw border (red if the selection is too small)
1212 pixmapPainter.setPen( selBlendColor );
1213 pixmapPainter.drawRect( selectionRect.adjusted( 0, 0, -1, -1 ) );
1215 // 3) Layer 1: give annotator painting control
1216 if ( d->annotator && d->annotator->routePaints( contentsRect ) )
1217 d->annotator->routePaint( &pixmapPainter, contentsRect );
1218 // 4) Layer 2: overlays
1219 if ( Okular::Settings::debugDrawBoundaries() )
1221 pixmapPainter.setPen( Qt::blue );
1222 pixmapPainter.drawRect( contentsRect );
1225 // finish painting and draw contents
1226 pixmapPainter.end();
1227 screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer );
1229 else
1231 // 1) Layer 0: paint items and clear bg on unpainted rects
1232 drawDocumentOnPainter( contentsRect, &screenPainter );
1233 // 2) Layer 1: paint opaque selection
1234 if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) &&
1235 !selectionRectInternal.contains( contentsRect ) )
1237 screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) );
1238 screenPainter.drawRect( selectionRect );
1240 // 3) Layer 1: give annotator painting control
1241 if ( d->annotator && d->annotator->routePaints( contentsRect ) )
1242 d->annotator->routePaint( &screenPainter, contentsRect );
1243 // 4) Layer 2: overlays
1244 if ( Okular::Settings::debugDrawBoundaries() )
1246 screenPainter.setPen( Qt::red );
1247 screenPainter.drawRect( contentsRect );
1253 void PageView::resizeEvent( QResizeEvent *e )
1255 if ( d->items.isEmpty() )
1257 widget()->resize(e->size());
1258 return;
1261 // start a timer that will refresh the pixmap after 0.2s
1262 if ( !d->delayResizeTimer )
1264 d->delayResizeTimer = new QTimer( this );
1265 d->delayResizeTimer->setSingleShot( true );
1266 connect( d->delayResizeTimer, SIGNAL( timeout() ), this, SLOT( slotRelayoutPages() ) );
1268 d->delayResizeTimer->start( 200 );
1271 void PageView::keyPressEvent( QKeyEvent * e )
1273 e->accept();
1275 // if performing a selection or dyn zooming, disable keys handling
1276 if ( ( d->mouseSelecting && e->key() != Qt::Key_Escape ) || d->mouseMidZooming )
1277 return;
1279 // if viewport is moving, disable keys handling
1280 if ( d->viewportMoveActive )
1281 return;
1283 // move/scroll page by using keys
1284 switch ( e->key() )
1286 case Qt::Key_Down:
1287 case Qt::Key_PageDown:
1288 case Qt::Key_Space:
1289 case Qt::Key_Up:
1290 case Qt::Key_PageUp:
1291 case Qt::Key_Backspace:
1292 if ( e->key() == Qt::Key_Down
1293 || e->key() == Qt::Key_PageDown
1294 || ( e->key() == Qt::Key_Space && ( e->modifiers() & Qt::ShiftModifier ) != Qt::ShiftModifier ) )
1296 // if in single page mode and at the bottom of the screen, go to next page
1297 if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() < verticalScrollBar()->maximum() )
1299 if ( e->key() == Qt::Key_Down )
1300 verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd );
1301 else
1302 verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepAdd );
1304 else if ( (int)d->document->currentPage() < d->items.count() - 1 )
1306 // more optimized than document->setNextPage and then move view to top
1307 Okular::DocumentViewport newViewport = d->document->viewport();
1308 newViewport.pageNumber += d->document->currentPage() ? viewColumns() : 1;
1309 if ( newViewport.pageNumber >= (int)d->items.count() )
1310 newViewport.pageNumber = d->items.count() - 1;
1311 newViewport.rePos.enabled = true;
1312 newViewport.rePos.normalizedY = 0.0;
1313 d->document->setViewport( newViewport );
1316 else
1318 // if in single page mode and at the top of the screen, go to \ page
1319 if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() > verticalScrollBar()->minimum() )
1321 if ( e->key() == Qt::Key_Up )
1322 verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub );
1323 else
1324 verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepSub );
1326 else if ( d->document->currentPage() > 0 )
1328 // more optimized than document->setPrevPage and then move view to bottom
1329 Okular::DocumentViewport newViewport = d->document->viewport();
1330 newViewport.pageNumber -= viewColumns();
1331 if ( newViewport.pageNumber < 0 )
1332 newViewport.pageNumber = 0;
1333 newViewport.rePos.enabled = true;
1334 newViewport.rePos.normalizedY = 1.0;
1335 d->document->setViewport( newViewport );
1338 break;
1339 case Qt::Key_Left:
1340 horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub );
1341 break;
1342 case Qt::Key_Right:
1343 horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd );
1344 break;
1345 case Qt::Key_Escape:
1346 selectionClear();
1347 d->mousePressPos = QPoint();
1348 if ( d->aPrevAction )
1350 d->aPrevAction->trigger();
1351 d->aPrevAction = 0;
1353 break;
1354 case Qt::Key_Shift:
1355 case Qt::Key_Control:
1356 if ( d->autoScrollTimer )
1358 if ( d->autoScrollTimer->isActive() )
1359 d->autoScrollTimer->stop();
1360 else
1361 slotAutoScoll();
1362 return;
1364 // else fall trhough
1365 default:
1366 e->ignore();
1367 return;
1369 // if a known key has been pressed, stop scrolling the page
1370 if ( d->autoScrollTimer )
1372 d->scrollIncrement = 0;
1373 d->autoScrollTimer->stop();
1377 void PageView::keyReleaseEvent( QKeyEvent * e )
1379 e->accept();
1381 if ( d->annotator && d->annotator->routeEvents() )
1383 if ( d->annotator->routeKeyEvent( e ) )
1384 return;
1387 if ( e->key() == Qt::Key_Escape && d->autoScrollTimer )
1389 d->scrollIncrement = 0;
1390 d->autoScrollTimer->stop();
1394 void PageView::inputMethodEvent( QInputMethodEvent * e )
1396 Q_UNUSED(e)
1399 static QPoint rotateInRect( const QPoint &rotated, Okular::Rotation rotation )
1401 QPoint ret;
1403 switch ( rotation )
1405 case Okular::Rotation90:
1406 ret = QPoint( rotated.y(), -rotated.x() );
1407 break;
1408 case Okular::Rotation180:
1409 ret = QPoint( -rotated.x(), -rotated.y() );
1410 break;
1411 case Okular::Rotation270:
1412 ret = QPoint( -rotated.y(), rotated.x() );
1413 break;
1414 case Okular::Rotation0: // no modifications
1415 default: // other cases
1416 ret = rotated;
1419 return ret;
1422 void PageView::contentsMouseMoveEvent( QMouseEvent * e )
1424 // don't perform any mouse action when no document is shown
1425 if ( d->items.isEmpty() )
1426 return;
1428 // don't perform any mouse action when viewport is autoscrolling
1429 if ( d->viewportMoveActive )
1430 return;
1432 // if holding mouse mid button, perform zoom
1433 if ( d->mouseMidZooming && (e->buttons() & Qt::MidButton) )
1435 int mouseY = e->globalPos().y();
1436 int deltaY = d->mouseMidLastY - mouseY;
1438 // wrap mouse from top to bottom
1439 QRect mouseContainer = KGlobalSettings::desktopGeometry( this );
1440 if ( mouseY <= mouseContainer.top() + 4 &&
1441 d->zoomFactor < 3.99 )
1443 mouseY = mouseContainer.bottom() - 5;
1444 QCursor::setPos( e->globalPos().x(), mouseY );
1446 // wrap mouse from bottom to top
1447 else if ( mouseY >= mouseContainer.bottom() - 4 &&
1448 d->zoomFactor > 0.11 )
1450 mouseY = mouseContainer.top() + 5;
1451 QCursor::setPos( e->globalPos().x(), mouseY );
1453 // remember last position
1454 d->mouseMidLastY = mouseY;
1456 // update zoom level, perform zoom and redraw
1457 if ( deltaY )
1459 d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) );
1460 updateZoom( ZoomRefreshCurrent );
1461 viewport()->repaint();
1463 return;
1466 // if we're editing an annotation, dispatch event to it
1467 if ( d->annotator && d->annotator->routeEvents() )
1469 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1470 d->annotator->routeEvent( e, pageItem );
1471 return;
1474 bool leftButton = (e->buttons() == Qt::LeftButton);
1475 bool rightButton = (e->buttons() == Qt::RightButton);
1476 switch ( d->mouseMode )
1478 case MouseNormal:
1479 if ( leftButton )
1481 if ( d->mouseAnn )
1483 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1484 if ( pageItem )
1486 const QRect & itemRect = pageItem->uncroppedGeometry();
1487 QPoint newpos = QPoint( e->x(), e->y() ) - itemRect.topLeft();
1488 Okular::NormalizedRect r = d->mouseAnn->boundingRectangle();
1489 QPoint p( newpos - d->mouseAnnPos );
1490 QPointF pf( rotateInRect( p, pageItem->page()->totalOrientation() ) );
1491 if ( pageItem->page()->totalOrientation() % 2 == 0 )
1493 pf.rx() /= pageItem->uncroppedWidth();
1494 pf.ry() /= pageItem->uncroppedHeight();
1496 else
1498 pf.rx() /= pageItem->uncroppedHeight();
1499 pf.ry() /= pageItem->uncroppedWidth();
1501 d->mouseAnn->translate( Okular::NormalizedPoint( pf.x(), pf.y() ) );
1502 d->mouseAnnPos = newpos;
1503 d->document->modifyPageAnnotation( pageItem->pageNumber(), d->mouseAnn );
1506 // drag page
1507 else if ( !d->mouseGrabPos.isNull() )
1509 QPoint mousePos = e->globalPos();
1510 QPoint delta = d->mouseGrabPos - mousePos;
1512 // wrap mouse from top to bottom
1513 QRect mouseContainer = KGlobalSettings::desktopGeometry( this );
1514 if ( mousePos.y() <= mouseContainer.top() + 4 &&
1515 verticalScrollBar()->value() < verticalScrollBar()->maximum() - 10 )
1517 mousePos.setY( mouseContainer.bottom() - 5 );
1518 QCursor::setPos( mousePos );
1520 // wrap mouse from bottom to top
1521 else if ( mousePos.y() >= mouseContainer.bottom() - 4 &&
1522 verticalScrollBar()->value() > 10 )
1524 mousePos.setY( mouseContainer.top() + 5 );
1525 QCursor::setPos( mousePos );
1527 // remember last position
1528 d->mouseGrabPos = mousePos;
1530 // scroll page by position increment
1531 horizontalScrollBar()->setValue(horizontalScrollBar()->value() + delta.x());
1532 verticalScrollBar()->setValue(verticalScrollBar()->value() + delta.y());
1535 else if ( rightButton && !d->mousePressPos.isNull() )
1537 // if mouse moves 5 px away from the press point, switch to 'selection'
1538 int deltaX = d->mousePressPos.x() - e->globalPos().x(),
1539 deltaY = d->mousePressPos.y() - e->globalPos().y();
1540 if ( deltaX > 5 || deltaX < -5 || deltaY > 5 || deltaY < -5 )
1542 d->aPrevAction = d->aMouseNormal;
1543 d->aMouseSelect->trigger();
1544 QPoint newPos(e->x() + deltaX, e->y() + deltaY);
1545 selectionStart( newPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false );
1546 selectionEndPoint( e->pos() );
1547 break;
1550 else
1552 // only hovering the page, so update the cursor
1553 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
1555 break;
1557 case MouseZoom:
1558 case MouseSelect:
1559 case MouseImageSelect:
1560 // set second corner of selection
1561 if ( d->mouseSelecting )
1562 selectionEndPoint( e->pos() );
1563 break;
1564 case MouseTextSelect:
1565 // if mouse moves 5 px away from the press point and the document soupports text extraction, do 'textselection'
1566 if ( !d->mouseTextSelecting && !d->mousePressPos.isNull() && d->document->supportsSearching() && ( ( e->pos() - d->mouseSelectPos ).manhattanLength() > 5 ) )
1568 d->mouseTextSelecting = true;
1570 if ( d->mouseTextSelecting )
1572 int first = -1;
1573 QList< Okular::RegularAreaRect * > selections = textSelections( e->pos(), d->mouseSelectPos, first );
1574 QSet< int > pagesWithSelectionSet;
1575 for ( int i = 0; i < selections.count(); ++i )
1576 pagesWithSelectionSet.insert( i + first );
1578 QSet< int > noMoreSelectedPages = d->pagesWithTextSelection - pagesWithSelectionSet;
1579 // clear the selection from pages not selected anymore
1580 foreach( int p, noMoreSelectedPages )
1582 d->document->setPageTextSelection( p, 0, QColor() );
1584 // set the new selection for the selected pages
1585 foreach( int p, pagesWithSelectionSet )
1587 d->document->setPageTextSelection( p, selections[ p - first ], palette().color( QPalette::Active, QPalette::Highlight ) );
1589 d->pagesWithTextSelection = pagesWithSelectionSet;
1591 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
1592 break;
1596 void PageView::contentsMousePressEvent( QMouseEvent * e )
1598 // don't perform any mouse action when no document is shown
1599 if ( d->items.isEmpty() )
1600 return;
1602 // if performing a selection or dyn zooming, disable mouse press
1603 if ( d->mouseSelecting || d->mouseMidZooming || d->viewportMoveActive )
1604 return;
1606 // if the page is scrolling, stop it
1607 if ( d->autoScrollTimer )
1609 d->scrollIncrement = 0;
1610 d->autoScrollTimer->stop();
1613 // if pressing mid mouse button while not doing other things, begin 'continuous zoom' mode
1614 if ( e->button() == Qt::MidButton )
1616 d->mouseMidZooming = true;
1617 d->mouseMidLastY = e->globalPos().y();
1618 setCursor( Qt::SizeVerCursor );
1619 return;
1622 // if we're editing an annotation, dispatch event to it
1623 if ( d->annotator && d->annotator->routeEvents() )
1625 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1626 d->annotator->routeEvent( e, pageItem );
1627 return;
1630 // update press / 'start drag' mouse position
1631 d->mousePressPos = e->globalPos();
1633 // handle mode dependant mouse press actions
1634 bool leftButton = e->button() == Qt::LeftButton,
1635 rightButton = e->button() == Qt::RightButton;
1637 // Not sure we should erase the selection when clicking with left.
1638 if ( d->mouseMode != MouseTextSelect )
1639 textSelectionClear();
1641 switch ( d->mouseMode )
1643 case MouseNormal: // drag start / click / link following
1644 if ( leftButton )
1646 PageViewItem * pageItem = 0;
1647 if ( ( e->modifiers() & Qt::ControlModifier ) && ( pageItem = pickItemOnPoint( e->x(), e->y() ) ) )
1649 // find out normalized mouse coords inside current item
1650 const QRect & itemRect = pageItem->uncroppedGeometry();
1651 double nX = pageItem->absToPageX(e->x());
1652 double nY = pageItem->absToPageY(e->y());
1653 const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() );
1654 d->mouseAnnPos = QPoint( e->x(), e->y() ) - itemRect.topLeft();
1655 if ( orect )
1656 d->mouseAnn = ( (Okular::AnnotationObjectRect *)orect )->annotation();
1657 // consider no annotation caught if its type is not movable
1658 if ( d->mouseAnn && !d->mouseAnn->canBeMoved() )
1659 d->mouseAnn = 0;
1661 if ( !d->mouseAnn )
1663 d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mousePressPos;
1664 if ( !d->mouseOnRect )
1665 setCursor( Qt::SizeAllCursor );
1668 else if ( rightButton )
1670 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1671 if ( pageItem )
1673 // find out normalized mouse coords inside current item
1674 const QRect & itemRect = pageItem->uncroppedGeometry();
1675 double nX = pageItem->absToPageX(e->x());
1676 double nY = pageItem->absToPageY(e->y());
1677 Okular::Annotation * ann = 0;
1678 const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() );
1679 if ( orect )
1680 ann = ( (Okular::AnnotationObjectRect *)orect )->annotation();
1681 if ( ann )
1683 AnnotationPopup popup( d->document, this );
1684 popup.addAnnotation( ann, pageItem->pageNumber() );
1686 connect( &popup, SIGNAL( setAnnotationWindow( Okular::Annotation* ) ),
1687 this, SLOT( setAnnotationWindow( Okular::Annotation* ) ) );
1688 connect( &popup, SIGNAL( removeAnnotationWindow( Okular::Annotation* ) ),
1689 this, SLOT( removeAnnotationWindow( Okular::Annotation* ) ) );
1691 popup.exec( e->globalPos() );
1695 break;
1697 case MouseZoom: // set first corner of the zoom rect
1698 if ( leftButton )
1699 selectionStart( e->pos(), palette().color( QPalette::Active, QPalette::Highlight ), false );
1700 else if ( rightButton )
1701 updateZoom( ZoomOut );
1702 break;
1704 case MouseSelect: // set first corner of the selection rect
1705 case MouseImageSelect:
1706 if ( leftButton )
1708 selectionStart( e->pos(), palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false );
1710 break;
1711 case MouseTextSelect:
1712 d->mouseSelectPos = e->pos();
1713 if ( !rightButton )
1715 textSelectionClear();
1717 break;
1721 void PageView::contentsMouseReleaseEvent( QMouseEvent * e )
1723 // stop the drag scrolling
1724 d->dragScrollTimer.stop();
1726 // don't perform any mouse action when no document is shown..
1727 if ( d->items.isEmpty() )
1729 // ..except for right Clicks (emitted even it viewport is empty)
1730 if ( e->button() == Qt::RightButton )
1731 emit rightClick( 0, e->globalPos() );
1732 return;
1735 // don't perform any mouse action when viewport is autoscrolling
1736 if ( d->viewportMoveActive )
1737 return;
1739 // handle mode indepent mid buttom zoom
1740 if ( d->mouseMidZooming && (e->button() == Qt::MidButton) )
1742 d->mouseMidZooming = false;
1743 // request pixmaps since it was disabled during drag
1744 slotRequestVisiblePixmaps();
1745 // the cursor may now be over a link.. update it
1746 updateCursor( e->pos() );
1747 return;
1750 // if we're editing an annotation, dispatch event to it
1751 if ( d->annotator && d->annotator->routeEvents() )
1753 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1754 d->annotator->routeEvent( e, pageItem );
1755 return;
1758 if ( d->mouseAnn )
1760 setCursor( Qt::ArrowCursor );
1761 d->mouseAnn = 0;
1764 bool leftButton = e->button() == Qt::LeftButton;
1765 bool rightButton = e->button() == Qt::RightButton;
1766 switch ( d->mouseMode )
1768 case MouseNormal:{
1769 // return the cursor to its normal state after dragging
1770 if ( cursor().shape() == Qt::SizeAllCursor )
1771 updateCursor( e->pos() );
1773 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1775 // if the mouse has not moved since the press, that's a -click-
1776 if ( leftButton && pageItem && d->mousePressPos == e->globalPos())
1778 double nX = pageItem->absToPageX(e->x());
1779 double nY = pageItem->absToPageY(e->y());
1780 const Okular::ObjectRect * rect;
1781 rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
1782 if ( rect )
1784 // handle click over a link
1785 const Okular::Action * action = static_cast< const Okular::Action * >( rect->object() );
1786 d->document->processAction( action );
1788 else
1790 // TODO: find a better way to activate the source reference "links"
1791 // for the moment they are activated with Shift + left click
1792 rect = e->modifiers() == Qt::ShiftModifier ? pageItem->page()->objectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ) : 0;
1793 if ( rect )
1795 const Okular::SourceReference * ref = static_cast< const Okular::SourceReference * >( rect->object() );
1796 d->document->processSourceReference( ref );
1798 #if 0
1799 // a link can move us to another page or even to another document, there's no point in trying to
1800 // process the click on the image once we have processes the click on the link
1801 rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->width(), pageItem->height() );
1802 if ( rect )
1804 // handle click over a image
1806 /* Enrico and me have decided this is not worth the trouble it generates
1807 else
1809 // if not on a rect, the click selects the page
1810 // if ( pageItem->pageNumber() != (int)d->document->currentPage() )
1811 d->document->setViewportPage( pageItem->pageNumber(), PAGEVIEW_ID );
1813 #endif
1816 else if ( rightButton )
1818 if ( pageItem && d->mousePressPos == e->globalPos() )
1820 double nX = pageItem->absToPageX(e->x());
1821 double nY = pageItem->absToPageY(e->y());
1822 const Okular::ObjectRect * rect;
1823 rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
1824 if ( rect )
1826 // handle right click over a link
1827 const Okular::Action * link = static_cast< const Okular::Action * >( rect->object() );
1828 // creating the menu and its actions
1829 KMenu menu( this );
1830 QAction * actProcessLink = menu.addAction( i18n( "Follow This Link" ) );
1831 QAction * actCopyLinkLocation = 0;
1832 if ( dynamic_cast< const Okular::BrowseAction * >( link ) )
1833 actCopyLinkLocation = menu.addAction( KIcon( "edit-copy" ), i18n( "Copy Link Address" ) );
1834 QAction * res = menu.exec( e->globalPos() );
1835 if ( res )
1837 if ( res == actProcessLink )
1839 d->document->processAction( link );
1841 else if ( res == actCopyLinkLocation )
1843 const Okular::BrowseAction * browseLink = static_cast< const Okular::BrowseAction * >( link );
1844 QClipboard *cb = QApplication::clipboard();
1845 cb->setText( browseLink->url(), QClipboard::Clipboard );
1846 if ( cb->supportsSelection() )
1847 cb->setText( browseLink->url(), QClipboard::Selection );
1851 else
1853 // a link can move us to another page or even to another document, there's no point in trying to
1854 // process the click on the image once we have processes the click on the link
1855 rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
1856 if ( rect )
1858 // handle right click over a image
1860 else
1862 // right click (if not within 5 px of the press point, the mode
1863 // had been already changed to 'Selection' instead of 'Normal')
1864 emit rightClick( pageItem->page(), e->globalPos() );
1868 else
1870 // right click (if not within 5 px of the press point, the mode
1871 // had been already changed to 'Selection' instead of 'Normal')
1872 emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() );
1875 }break;
1877 case MouseZoom:
1878 // if a selection rect has been defined, zoom into it
1879 if ( leftButton && d->mouseSelecting )
1881 QRect selRect = d->mouseSelectionRect.normalized();
1882 if ( selRect.width() <= 8 && selRect.height() <= 8 )
1884 selectionClear();
1885 break;
1888 // find out new zoom ratio and normalized view center (relative to the contentsRect)
1889 double zoom = qMin( (double)viewport()->width() / (double)selRect.width(), (double)viewport()->height() / (double)selRect.height() );
1890 double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)widget()->width());
1891 double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)widget()->height());
1893 // zoom up to 400%
1894 if ( d->zoomFactor <= 4.0 || zoom <= 1.0 )
1896 d->zoomFactor *= zoom;
1897 viewport()->setUpdatesEnabled( false );
1898 updateZoom( ZoomRefreshCurrent );
1899 viewport()->setUpdatesEnabled( true );
1902 // recenter view and update the viewport
1903 center( (int)(nX * widget()->width()), (int)(nY * widget()->height()) );
1904 widget()->update();
1906 // hide message box and delete overlay window
1907 selectionClear();
1909 break;
1911 case MouseSelect:
1912 case MouseImageSelect:
1914 // if mouse is released and selection is null this is a rightClick
1915 if ( rightButton && !d->mouseSelecting )
1917 PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() );
1918 emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() );
1919 break;
1922 // if a selection is defined, display a popup
1923 if ( (!leftButton && !d->aPrevAction) || (!rightButton && d->aPrevAction) ||
1924 !d->mouseSelecting )
1925 break;
1927 QRect selectionRect = d->mouseSelectionRect.normalized();
1928 if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 )
1930 selectionClear();
1931 if ( d->aPrevAction )
1933 d->aPrevAction->trigger();
1934 d->aPrevAction = 0;
1936 break;
1939 // if we support text generation
1940 QString selectedText;
1941 if (d->document->supportsSearching())
1943 // grab text in selection by extracting it from all intersected pages
1944 const Okular::Page * okularPage=0;
1945 QVector< PageViewItem * >::const_iterator iIt = d->items.begin(), iEnd = d->items.end();
1946 for ( ; iIt != iEnd; ++iIt )
1948 PageViewItem * item = *iIt;
1949 if ( !item->isVisible() )
1950 continue;
1952 const QRect & itemRect = item->croppedGeometry();
1953 if ( selectionRect.intersects( itemRect ) )
1955 // request the textpage if there isn't one
1956 okularPage= item->page();
1957 kWarning() << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage();
1958 if ( !okularPage->hasTextPage() )
1959 d->document->requestTextPage( okularPage->number() );
1960 // grab text in the rect that intersects itemRect
1961 QRect relativeRect = selectionRect.intersect( itemRect );
1962 relativeRect.translate( -item->uncroppedGeometry().topLeft() );
1963 Okular::RegularAreaRect rects;
1964 rects.append( Okular::NormalizedRect( relativeRect, item->uncroppedWidth(), item->uncroppedHeight() ) );
1965 selectedText += okularPage->text( &rects );
1970 // popup that ask to copy:text and copy/save:image
1971 KMenu menu( this );
1972 QAction *textToClipboard = 0, *speakText = 0, *imageToClipboard = 0, *imageToFile = 0;
1973 if ( d->document->supportsSearching() && !selectedText.isEmpty() )
1975 menu.addTitle( i18np( "Text (1 character)", "Text (%1 characters)", selectedText.length() ) );
1976 textToClipboard = menu.addAction( KIcon("edit-copy"), i18n( "Copy to Clipboard" ) );
1977 if ( !d->document->isAllowed( Okular::AllowCopy ) )
1979 textToClipboard->setEnabled( false );
1980 textToClipboard->setText( i18n("Copy forbidden by DRM") );
1982 if ( Okular::Settings::useKTTSD() )
1983 speakText = menu.addAction( KIcon("text-speak"), i18n( "Speak Text" ) );
1985 menu.addTitle( i18n( "Image (%1 by %2 pixels)", selectionRect.width(), selectionRect.height() ) );
1986 imageToClipboard = menu.addAction( KIcon("image-x-generic"), i18n( "Copy to Clipboard" ) );
1987 imageToFile = menu.addAction( KIcon("document-save"), i18n( "Save to File..." ) );
1988 QAction *choice = menu.exec( e->globalPos() );
1989 // check if the user really selected an action
1990 if ( choice )
1992 // IMAGE operation chosen
1993 if ( choice == imageToClipboard || choice == imageToFile )
1995 // renders page into a pixmap
1996 QPixmap copyPix( selectionRect.width(), selectionRect.height() );
1997 QPainter copyPainter( &copyPix );
1998 copyPainter.translate( -selectionRect.left(), -selectionRect.top() );
1999 drawDocumentOnPainter( selectionRect, &copyPainter );
2000 copyPainter.end();
2002 if ( choice == imageToClipboard )
2004 // [2] copy pixmap to clipboard
2005 QClipboard *cb = QApplication::clipboard();
2006 cb->setPixmap( copyPix, QClipboard::Clipboard );
2007 if ( cb->supportsSelection() )
2008 cb->setPixmap( copyPix, QClipboard::Selection );
2009 d->messageWindow->display( i18n( "Image [%1x%2] copied to clipboard.", copyPix.width(), copyPix.height() ) );
2011 else if ( choice == imageToFile )
2013 // [3] save pixmap to file
2014 QString fileName = KFileDialog::getSaveFileName( KUrl(), "image/png image/jpeg", this );
2015 if ( fileName.isEmpty() )
2016 d->messageWindow->display( i18n( "File not saved." ), PageViewMessage::Warning );
2017 else
2019 KMimeType::Ptr mime = KMimeType::findByUrl( fileName );
2020 QString type;
2021 if ( !mime )
2022 type = "PNG";
2023 else
2024 type = mime->name().section( '/', -1 ).toUpper();
2025 copyPix.save( fileName, qPrintable( type ) );
2026 d->messageWindow->display( i18n( "Image [%1x%2] saved to %3 file.", copyPix.width(), copyPix.height(), type ) );
2030 // TEXT operation chosen
2031 else
2033 if ( choice == textToClipboard )
2035 // [1] copy text to clipboard
2036 QClipboard *cb = QApplication::clipboard();
2037 cb->setText( selectedText, QClipboard::Clipboard );
2038 if ( cb->supportsSelection() )
2039 cb->setText( selectedText, QClipboard::Selection );
2041 else if ( choice == speakText )
2043 // [2] speech selection using KTTSD
2044 d->tts()->say( selectedText );
2048 // clear widget selection and invalidate rect
2049 selectionClear();
2051 // restore previous action if came from it using right button
2052 if ( d->aPrevAction )
2054 d->aPrevAction->trigger();
2055 d->aPrevAction = 0;
2057 }break;
2058 case MouseTextSelect:
2059 setCursor( Qt::ArrowCursor );
2060 if ( d->mouseTextSelecting )
2062 d->mouseTextSelecting = false;
2063 // textSelectionClear();
2064 if ( d->document->isAllowed( Okular::AllowCopy ) )
2066 const QString text = d->selectedText();
2067 if ( !text.isEmpty() )
2069 QClipboard *cb = QApplication::clipboard();
2070 if ( cb->supportsSelection() )
2071 cb->setText( text, QClipboard::Selection );
2075 else if ( !d->mousePressPos.isNull() && rightButton )
2077 KMenu menu( this );
2078 QAction *textToClipboard = menu.addAction( KIcon( "edit-copy" ), i18n( "Copy Text" ) );
2079 QAction *speakText = 0;
2080 if ( Okular::Settings::useKTTSD() )
2081 speakText = menu.addAction( KIcon( "text-speak" ), i18n( "Speak Text" ) );
2082 if ( !d->document->isAllowed( Okular::AllowCopy ) )
2084 textToClipboard->setEnabled( false );
2085 textToClipboard->setText( i18n("Copy forbidden by DRM") );
2087 QAction *choice = menu.exec( e->globalPos() );
2088 // check if the user really selected an action
2089 if ( choice )
2091 if ( choice == textToClipboard )
2092 copyTextSelection();
2093 else if ( choice == speakText )
2095 const QString text = d->selectedText();
2096 d->tts()->say( text );
2100 break;
2103 // reset mouse press / 'drag start' position
2104 d->mousePressPos = QPoint();
2107 void PageView::wheelEvent( QWheelEvent *e )
2109 // don't perform any mouse action when viewport is autoscrolling
2110 if ( d->viewportMoveActive )
2111 return;
2113 if ( !d->document->isOpened() )
2115 QScrollArea::wheelEvent( e );
2116 return;
2119 int delta = e->delta(),
2120 vScroll = verticalScrollBar()->value();
2121 e->accept();
2122 if ( (e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) {
2123 if ( e->delta() < 0 )
2124 slotZoomOut();
2125 else
2126 slotZoomIn();
2128 else if ( delta <= -120 && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->maximum() )
2130 // go to next page
2131 if ( (int)d->document->currentPage() < d->items.count() - 1 )
2133 // more optimized than document->setNextPage and then move view to top
2134 Okular::DocumentViewport newViewport = d->document->viewport();
2135 newViewport.pageNumber += d->document->currentPage() ? viewColumns() : 1;
2136 if ( newViewport.pageNumber >= (int)d->items.count() )
2137 newViewport.pageNumber = d->items.count() - 1;
2138 newViewport.rePos.enabled = true;
2139 newViewport.rePos.normalizedY = 0.0;
2140 d->document->setViewport( newViewport );
2143 else if ( delta >= 120 && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->minimum() )
2145 // go to prev page
2146 if ( d->document->currentPage() > 0 )
2148 // more optimized than document->setPrevPage and then move view to bottom
2149 Okular::DocumentViewport newViewport = d->document->viewport();
2150 newViewport.pageNumber -= viewColumns();
2151 if ( newViewport.pageNumber < 0 )
2152 newViewport.pageNumber = 0;
2153 newViewport.rePos.enabled = true;
2154 newViewport.rePos.normalizedY = 1.0;
2155 d->document->setViewport( newViewport );
2158 else
2159 QScrollArea::wheelEvent( e );
2161 QPoint cp = widget()->mapFromGlobal(mapToGlobal(e->pos()));
2162 updateCursor(cp);
2165 void PageView::dragEnterEvent( QDragEnterEvent * ev )
2167 ev->accept();
2170 void PageView::dragMoveEvent( QDragMoveEvent * ev )
2172 ev->accept();
2175 void PageView::dropEvent( QDropEvent * ev )
2177 if ( KUrl::List::canDecode( ev->mimeData() ) )
2178 emit urlDropped( KUrl::List::fromMimeData( ev->mimeData() ).first() );
2180 //END widget events
2182 QList< Okular::RegularAreaRect * > PageView::textSelections( const QPoint& start, const QPoint& end, int& firstpage )
2184 firstpage = -1;
2185 QList< Okular::RegularAreaRect * > ret;
2186 QSet< int > affectedItemsSet;
2187 QRect selectionRect = QRect( start, end ).normalized();
2188 foreach( PageViewItem * item, d->items )
2190 if ( item->isVisible() && selectionRect.intersects( item->croppedGeometry() ) )
2191 affectedItemsSet.insert( item->pageNumber() );
2193 #ifdef PAGEVIEW_DEBUG
2194 kDebug() << ">>>> item selected by mouse:" << affectedItemsSet.count();
2195 #endif
2197 if ( !affectedItemsSet.isEmpty() )
2199 // is the mouse drag line the ne-sw diagonal of the selection rect?
2200 bool direction_ne_sw = start == selectionRect.topRight() || start == selectionRect.bottomLeft();
2202 int tmpmin = d->document->pages();
2203 int tmpmax = 0;
2204 foreach( int p, affectedItemsSet )
2206 if ( p < tmpmin ) tmpmin = p;
2207 if ( p > tmpmax ) tmpmax = p;
2210 PageViewItem * a = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.right() : selectionRect.left() ), (int)selectionRect.top() );
2211 int min = a && ( a->pageNumber() != tmpmax ) ? a->pageNumber() : tmpmin;
2212 PageViewItem * b = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.left() : selectionRect.right() ), (int)selectionRect.bottom() );
2213 int max = b && ( b->pageNumber() != tmpmin ) ? b->pageNumber() : tmpmax;
2215 QList< int > affectedItemsIds;
2216 for ( int i = min; i <= max; ++i )
2217 affectedItemsIds.append( i );
2218 #ifdef PAGEVIEW_DEBUG
2219 kDebug() << ">>>> pages:" << affectedItemsIds;
2220 #endif
2221 firstpage = affectedItemsIds.first();
2223 if ( affectedItemsIds.count() == 1 )
2225 PageViewItem * item = d->items[ affectedItemsIds.first() ];
2226 selectionRect.translate( -item->uncroppedGeometry().topLeft() );
2227 ret.append( textSelectionForItem( item,
2228 direction_ne_sw ? selectionRect.topRight() : selectionRect.topLeft(),
2229 direction_ne_sw ? selectionRect.bottomLeft() : selectionRect.bottomRight() ) );
2231 else if ( affectedItemsIds.count() > 1 )
2233 // first item
2234 PageViewItem * first = d->items[ affectedItemsIds.first() ];
2235 QRect geom = first->croppedGeometry().intersect( selectionRect ).translated( -first->uncroppedGeometry().topLeft() );
2236 ret.append( textSelectionForItem( first,
2237 selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.topRight() : geom.topLeft() ) : ( direction_ne_sw ? geom.bottomRight() : geom.bottomLeft() ),
2238 QPoint() ) );
2239 // last item
2240 PageViewItem * last = d->items[ affectedItemsIds.last() ];
2241 geom = last->croppedGeometry().intersect( selectionRect ).translated( -last->uncroppedGeometry().topLeft() );
2242 // the last item needs to appended at last...
2243 Okular::RegularAreaRect * lastArea = textSelectionForItem( last,
2244 QPoint(),
2245 selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.bottomLeft() : geom.bottomRight() ) : ( direction_ne_sw ? geom.topLeft() : geom.topRight() ) );
2246 affectedItemsIds.removeFirst();
2247 affectedItemsIds.removeLast();
2248 // item between the two above
2249 foreach( int page, affectedItemsIds )
2251 ret.append( textSelectionForItem( d->items[ page ] ) );
2253 ret.append( lastArea );
2256 return ret;
2260 void PageView::drawDocumentOnPainter( const QRect & contentsRect, QPainter * p )
2262 // when checking if an Item is contained in contentsRect, instead of
2263 // growing PageViewItems rects (for keeping outline into account), we
2264 // grow the contentsRect
2265 QRect checkRect = contentsRect;
2266 checkRect.adjust( -3, -3, 1, 1 );
2268 // create a region from which we'll subtract painted rects
2269 QRegion remainingArea( contentsRect );
2271 // iterate over all items painting the ones intersecting contentsRect
2272 QVector< PageViewItem * >::const_iterator iIt = d->items.begin(), iEnd = d->items.end();
2273 for ( ; iIt != iEnd; ++iIt )
2275 // check if a piece of the page intersects the contents rect
2276 if ( !(*iIt)->isVisible() || !(*iIt)->croppedGeometry().intersects( checkRect ) )
2277 continue;
2279 // get item and item's outline geometries
2280 PageViewItem * item = *iIt;
2281 QRect itemGeometry = item->croppedGeometry(),
2282 outlineGeometry = itemGeometry;
2283 outlineGeometry.adjust( -1, -1, 3, 3 );
2285 // move the painter to the top-left corner of the real page
2286 p->save();
2287 p->translate( itemGeometry.left(), itemGeometry.top() );
2289 // draw the page outline (black border and 2px bottom-right shadow)
2290 if ( !itemGeometry.contains( contentsRect ) )
2292 int itemWidth = itemGeometry.width(),
2293 itemHeight = itemGeometry.height();
2294 // draw simple outline
2295 p->setPen( Qt::black );
2296 p->drawRect( -1, -1, itemWidth + 1, itemHeight + 1 );
2297 // draw bottom/right gradient
2298 static int levels = 2;
2299 int r = QColor(Qt::gray).red() / (levels + 2),
2300 g = QColor(Qt::gray).green() / (levels + 2),
2301 b = QColor(Qt::gray).blue() / (levels + 2);
2302 for ( int i = 0; i < levels; i++ )
2304 p->setPen( QColor( r * (i+2), g * (i+2), b * (i+2) ) );
2305 p->drawLine( i, i + itemHeight + 1, i + itemWidth + 1, i + itemHeight + 1 );
2306 p->drawLine( i + itemWidth + 1, i, i + itemWidth + 1, i + itemHeight );
2307 p->setPen( Qt::gray );
2308 p->drawLine( -1, i + itemHeight + 1, i - 1, i + itemHeight + 1 );
2309 p->drawLine( i + itemWidth + 1, -1, i + itemWidth + 1, i - 1 );
2313 // draw the page using the PagePainter with all flags active
2314 if ( contentsRect.intersects( itemGeometry ) )
2316 QRect pixmapRect = contentsRect.intersect( itemGeometry );
2317 pixmapRect.translate( -item->croppedGeometry().topLeft() );
2318 PagePainter::paintCroppedPageOnPainter( p, item->page(), PAGEVIEW_ID, pageflags,
2319 item->uncroppedWidth(), item->uncroppedHeight(), pixmapRect,
2320 item->crop() );
2323 // remove painted area from 'remainingArea' and restore painter
2324 remainingArea -= outlineGeometry.intersect( contentsRect );
2325 p->restore();
2328 // fill with background color the unpainted area
2329 const QVector<QRect> &backRects = remainingArea.rects();
2330 int backRectsNumber = backRects.count();
2331 // the previous color here was Qt::gray
2332 QColor backColor = widget()->palette().color( QPalette::Dark );
2333 for ( int jr = 0; jr < backRectsNumber; jr++ )
2334 p->fillRect( backRects[ jr ], backColor );
2337 void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight )
2339 const Okular::Page * okularPage = item->page();
2340 double width = okularPage->width(),
2341 height = okularPage->height(),
2342 zoom = d->zoomFactor;
2343 Okular::NormalizedRect crop( 0., 0., 1., 1. );
2345 // Handle cropping
2346 if ( Okular::Settings::trimMargins() && okularPage->isBoundingBoxKnown()
2347 && !okularPage->boundingBox().isNull() )
2349 crop = okularPage->boundingBox();
2351 // Rotate the bounding box from upright Rotation0 to current page orientation:
2352 for ( int i = okularPage->totalOrientation(); i > 0; --i )
2354 Okular::NormalizedRect rot = crop;
2355 crop.left = 1 - rot.bottom;
2356 crop.top = rot.left;
2357 crop.right = 1 - rot.top;
2358 crop.bottom = rot.right;
2361 // Expand the crop slightly beyond the bounding box
2362 static const double cropExpandRatio = 0.04;
2363 double cropExpand = cropExpandRatio * ( (crop.right-crop.left) + (crop.bottom-crop.top) ) / 2;
2364 crop = Okular::NormalizedRect(
2365 crop.left - cropExpand,
2366 crop.top - cropExpand,
2367 crop.right + cropExpand,
2368 crop.bottom + cropExpand ) & Okular::NormalizedRect( 0, 0, 1, 1 );
2370 // We currently generate a larger image and then crop it, so if the
2371 // crop rect is very small the generated image is huge. Hence, we shouldn't
2372 // let the crop rect become too small.
2373 // Make sure we crop by at most 50% in either dimension:
2374 static const double minCropRatio = 0.5;
2375 if ( ( crop.right - crop.left ) < minCropRatio )
2377 double newLeft = ( crop.left + crop.right ) / 2 - minCropRatio/2;
2378 crop.left = qMax( 0.0, qMin( 1.0 - minCropRatio, newLeft ) );
2379 crop.right = crop.left + minCropRatio;
2381 if ( ( crop.bottom - crop.top ) < minCropRatio )
2383 double newTop = ( crop.top + crop.bottom ) / 2 - minCropRatio/2;
2384 crop.top = qMax( 0.0, qMin( 1.0 - minCropRatio, newTop ) );
2385 crop.bottom = crop.top + minCropRatio;
2388 width *= ( crop.right - crop.left );
2389 height *= ( crop.bottom - crop.top );
2390 #ifdef PAGEVIEW_DEBUG
2391 kDebug() << "Cropped page" << okularPage->number() << "to" << crop
2392 << "width" << width << "height" << height << "by bbox" << okularPage->boundingBox();
2393 #endif
2396 if ( d->zoomMode == ZoomFixed )
2398 width *= zoom;
2399 height *= zoom;
2400 item->setWHZC( (int)width, (int)height, d->zoomFactor, crop );
2402 else if ( d->zoomMode == ZoomFitWidth )
2404 height = ( height / width ) * colWidth;
2405 zoom = (double)colWidth / width;
2406 item->setWHZC( colWidth, (int)height, zoom, crop );
2407 d->zoomFactor = zoom;
2409 else if ( d->zoomMode == ZoomFitPage )
2411 double scaleW = (double)colWidth / (double)width;
2412 double scaleH = (double)rowHeight / (double)height;
2413 zoom = qMin( scaleW, scaleH );
2414 item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop );
2415 d->zoomFactor = zoom;
2417 #ifndef NDEBUG
2418 else
2419 kDebug() << "calling updateItemSize with unrecognized d->zoomMode!";
2420 #endif
2423 PageViewItem * PageView::pickItemOnPoint( int x, int y )
2425 PageViewItem * item = 0;
2426 QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd();
2427 for ( ; iIt != iEnd; ++iIt )
2429 PageViewItem * i = *iIt;
2430 const QRect & r = i->croppedGeometry();
2431 if ( x < r.right() && x > r.left() && y < r.bottom() )
2433 if ( y > r.top() )
2434 item = i;
2435 break;
2438 return item;
2441 void PageView::textSelectionClear()
2443 // something to clear
2444 if ( !d->pagesWithTextSelection.isEmpty() )
2446 QSet< int >::ConstIterator it = d->pagesWithTextSelection.constBegin(), itEnd = d->pagesWithTextSelection.constEnd();
2447 for ( ; it != itEnd; ++it )
2448 d->document->setPageTextSelection( *it, 0, QColor() );
2449 d->pagesWithTextSelection.clear();
2453 void PageView::selectionStart( const QPoint & pos, const QColor & color, bool /*aboveAll*/ )
2455 selectionClear();
2456 d->mouseSelecting = true;
2457 d->mouseSelectionRect.setRect( pos.x(), pos.y(), 1, 1 );
2458 d->mouseSelectionColor = color;
2459 // ensures page doesn't scroll
2460 if ( d->autoScrollTimer )
2462 d->scrollIncrement = 0;
2463 d->autoScrollTimer->stop();
2467 void PageView::selectionEndPoint( const QPoint & pos )
2469 if ( !d->mouseSelecting )
2470 return;
2472 if (pos.x() < horizontalScrollBar()->value()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value());
2473 else if (horizontalScrollBar()->value() + viewport()->width() < pos.x()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value() - viewport()->width());
2474 else d->dragScrollVector.setX(0);
2476 if (pos.y() < verticalScrollBar()->value()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value());
2477 else if (verticalScrollBar()->value() + viewport()->height() < pos.y()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value() - viewport()->height());
2478 else d->dragScrollVector.setY(0);
2480 if (d->dragScrollVector != QPoint(0, 0))
2482 if (!d->dragScrollTimer.isActive()) d->dragScrollTimer.start(100);
2484 else d->dragScrollTimer.stop();
2486 // update the selection rect
2487 QRect updateRect = d->mouseSelectionRect;
2488 d->mouseSelectionRect.setBottomLeft( pos );
2489 updateRect |= d->mouseSelectionRect;
2490 widget()->update( updateRect.adjusted( -1, -1, 1, 1 ) );
2493 static Okular::NormalizedPoint rotateInNormRect( const QPoint &rotated, const QRect &rect, Okular::Rotation rotation )
2495 Okular::NormalizedPoint ret;
2497 switch ( rotation )
2499 case Okular::Rotation0:
2500 ret = Okular::NormalizedPoint( rotated.x(), rotated.y(), rect.width(), rect.height() );
2501 break;
2502 case Okular::Rotation90:
2503 ret = Okular::NormalizedPoint( rotated.y(), rect.width() - rotated.x(), rect.height(), rect.width() );
2504 break;
2505 case Okular::Rotation180:
2506 ret = Okular::NormalizedPoint( rect.width() - rotated.x(), rect.height() - rotated.y(), rect.width(), rect.height() );
2507 break;
2508 case Okular::Rotation270:
2509 ret = Okular::NormalizedPoint( rect.height() - rotated.y(), rotated.x(), rect.height(), rect.width() );
2510 break;
2513 return ret;
2516 Okular::RegularAreaRect * PageView::textSelectionForItem( PageViewItem * item, const QPoint & startPoint, const QPoint & endPoint )
2518 const QRect & geometry = item->uncroppedGeometry();
2519 Okular::NormalizedPoint startCursor( 0.0, 0.0 );
2520 if ( !startPoint.isNull() )
2522 startCursor = rotateInNormRect( startPoint, geometry, item->page()->rotation() );
2524 Okular::NormalizedPoint endCursor( 1.0, 1.0 );
2525 if ( !endPoint.isNull() )
2527 endCursor = rotateInNormRect( endPoint, geometry, item->page()->rotation() );
2529 Okular::TextSelection mouseTextSelectionInfo( startCursor, endCursor );
2531 const Okular::Page * okularPage = item->page();
2533 if ( !okularPage->hasTextPage() )
2534 d->document->requestTextPage( okularPage->number() );
2536 Okular::RegularAreaRect * selectionArea = okularPage->textArea( &mouseTextSelectionInfo );
2537 #ifdef PAGEVIEW_DEBUG
2538 kDebug().nospace() << "text areas (" << okularPage->number() << "): " << ( selectionArea ? QString::number( selectionArea->count() ) : "(none)" );
2539 #endif
2540 return selectionArea;
2543 void PageView::selectionClear()
2545 QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( 0, 0, 1, 1 );
2546 d->mouseSelecting = false;
2547 d->mouseSelectionRect.setCoords( 0, 0, 0, 0 );
2548 widget()->update( updatedRect );
2551 void PageView::updateZoom( ZoomMode newZoomMode )
2553 if ( newZoomMode == ZoomFixed )
2555 if ( d->aZoom->currentItem() == 0 )
2556 newZoomMode = ZoomFitWidth;
2557 else if ( d->aZoom->currentItem() == 1 )
2558 newZoomMode = ZoomFitPage;
2561 float newFactor = d->zoomFactor;
2562 QAction * checkedZoomAction = 0;
2563 switch ( newZoomMode )
2565 case ZoomFixed:{ //ZoomFixed case
2566 QString z = d->aZoom->currentText();
2567 // kdelibs4 sometimes adds accelerators to actions' text directly :(
2568 z.remove ('&');
2569 z.remove ('%');
2570 newFactor = KGlobal::locale()->readNumber( z ) / 100.0;
2571 }break;
2572 case ZoomIn:
2573 newFactor += (newFactor > 0.99) ? ( newFactor > 1.99 ? 0.5 : 0.2 ) : 0.1;
2574 newZoomMode = ZoomFixed;
2575 break;
2576 case ZoomOut:
2577 newFactor -= (newFactor > 1.01) ? ( newFactor > 2.01 ? 0.5 : 0.2 ) : 0.1;
2578 newZoomMode = ZoomFixed;
2579 break;
2580 case ZoomFitWidth:
2581 checkedZoomAction = d->aZoomFitWidth;
2582 break;
2583 case ZoomFitPage:
2584 checkedZoomAction = d->aZoomFitPage;
2585 break;
2586 case ZoomFitText:
2587 checkedZoomAction = d->aZoomFitText;
2588 break;
2589 case ZoomRefreshCurrent:
2590 newZoomMode = ZoomFixed;
2591 d->zoomFactor = -1;
2592 break;
2594 if ( newFactor > 4.0 )
2595 newFactor = 4.0;
2596 if ( newFactor < 0.1 )
2597 newFactor = 0.1;
2599 if ( newZoomMode != d->zoomMode || (newZoomMode == ZoomFixed && newFactor != d->zoomFactor ) )
2601 // rebuild layout and update the whole viewport
2602 d->zoomMode = newZoomMode;
2603 d->zoomFactor = newFactor;
2604 // be sure to block updates to document's viewport
2605 bool prevState = d->blockViewport;
2606 d->blockViewport = true;
2607 slotRelayoutPages();
2608 d->blockViewport = prevState;
2609 // request pixmaps
2610 slotRequestVisiblePixmaps();
2611 // update zoom text
2612 updateZoomText();
2613 // update actions checked state
2614 if ( d->aZoomFitWidth )
2616 d->aZoomFitWidth->setChecked( checkedZoomAction == d->aZoomFitWidth );
2617 d->aZoomFitPage->setChecked( checkedZoomAction == d->aZoomFitPage );
2618 // d->aZoomFitText->setChecked( checkedZoomAction == d->aZoomFitText );
2622 d->aZoomIn->setEnabled( d->zoomFactor < 3.9 );
2623 d->aZoomOut->setEnabled( d->zoomFactor > 0.2 );
2626 void PageView::updateZoomText()
2628 // use current page zoom as zoomFactor if in ZoomFit/* mode
2629 if ( d->zoomMode != ZoomFixed && d->items.count() > 0 )
2630 d->zoomFactor = d->items[ qMax( 0, (int)d->document->currentPage() ) ]->zoomFactor();
2631 float newFactor = d->zoomFactor;
2632 d->aZoom->removeAllActions();
2634 // add items that describe fit actions
2635 QStringList translated;
2636 translated << i18n("Fit Width") << i18n("Fit Page") /*<< i18n("Fit Text")*/;
2638 // add percent items
2639 QString double_oh( "00" );
2640 const float zoomValue[10] = { 0.12, 0.25, 0.33, 0.50, 0.66, 0.75, 1.00, 1.25, 1.50, 2.00 };
2641 int idx = 0,
2642 selIdx = 2; // use 3 if "fit text" present
2643 bool inserted = false; //use: "d->zoomMode != ZoomFixed" to hide Fit/* zoom ratio
2644 while ( idx < 10 || !inserted )
2646 float value = idx < 10 ? zoomValue[ idx ] : newFactor;
2647 if ( !inserted && newFactor < (value - 0.0001) )
2648 value = newFactor;
2649 else
2650 idx ++;
2651 if ( value > (newFactor - 0.0001) && value < (newFactor + 0.0001) )
2652 inserted = true;
2653 if ( !inserted )
2654 selIdx++;
2655 QString localValue( KGlobal::locale()->formatNumber( value * 100.0, 2 ) );
2656 localValue.remove( KGlobal::locale()->decimalSymbol() + double_oh );
2657 // remove a trailing zero in numbers like 66.70
2658 if ( localValue.right( 1 ) == QLatin1String( "0" ) && localValue.indexOf( KGlobal::locale()->decimalSymbol() ) > -1 )
2659 localValue.chop( 1 );
2660 translated << QString( "%1%" ).arg( localValue );
2662 d->aZoom->setItems( translated );
2664 // select current item in list
2665 if ( d->zoomMode == ZoomFitWidth )
2666 selIdx = 0;
2667 else if ( d->zoomMode == ZoomFitPage )
2668 selIdx = 1;
2669 else if ( d->zoomMode == ZoomFitText )
2670 selIdx = 2;
2671 d->aZoom->setCurrentItem( selIdx );
2674 void PageView::updateCursor( const QPoint &p )
2676 // detect the underlaying page (if present)
2677 PageViewItem * pageItem = pickItemOnPoint( p.x(), p.y() );
2678 if ( pageItem )
2680 double nX = pageItem->absToPageX(p.x());
2681 double nY = pageItem->absToPageY(p.y());
2683 // if over a ObjectRect (of type Link) change cursor to hand
2684 if ( d->mouseMode == MouseTextSelect )
2685 setCursor( Qt::IBeamCursor );
2686 else if ( d->mouseAnn )
2687 setCursor( Qt::ClosedHandCursor );
2688 else
2690 const Okular::ObjectRect * linkobj = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
2691 const Okular::ObjectRect * annotobj = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() );
2692 if ( linkobj && !annotobj )
2694 d->mouseOnRect = true;
2695 setCursor( Qt::PointingHandCursor );
2697 else
2699 d->mouseOnRect = false;
2700 if ( annotobj
2701 && ( QApplication::keyboardModifiers() & Qt::ControlModifier )
2702 && static_cast< const Okular::AnnotationObjectRect * >( annotobj )->annotation()->canBeMoved() )
2704 setCursor( Qt::OpenHandCursor );
2706 else
2708 setCursor( Qt::ArrowCursor );
2713 else
2715 // if there's no page over the cursor and we were showing the pointingHandCursor
2716 // go back to the normal one
2717 d->mouseOnRect = false;
2718 setCursor( Qt::ArrowCursor );
2722 int PageView::viewColumns() const
2724 int nr = Okular::Settings::viewMode();
2725 if (nr<2)
2726 return nr+1;
2727 return Okular::Settings::viewColumns();
2730 int PageView::viewRows() const
2732 if ( Okular::Settings::viewMode() < 2 )
2733 return 1;
2734 return Okular::Settings::viewRows();
2737 void PageView::center(int cx, int cy)
2739 horizontalScrollBar()->setValue(cx - viewport()->width() / 2);
2740 verticalScrollBar()->setValue(cy - viewport()->height() / 2);
2743 void PageView::toggleFormWidgets( bool on )
2745 bool somehadfocus = false;
2746 QVector< PageViewItem * >::const_iterator dIt = d->items.begin(), dEnd = d->items.end();
2747 for ( ; dIt != dEnd; ++dIt )
2749 bool hadfocus = (*dIt)->setFormWidgetsVisible( on );
2750 somehadfocus = somehadfocus || hadfocus;
2752 if ( somehadfocus )
2753 setFocus();
2754 d->m_formsVisible = on;
2755 if ( d->aToggleForms ) // it may not exist if we are on dummy mode
2757 if ( d->m_formsVisible )
2759 d->aToggleForms->setText( i18n( "Hide Forms" ) );
2761 else
2763 d->aToggleForms->setText( i18n( "Show Forms" ) );
2768 //BEGIN private SLOTS
2769 void PageView::slotRelayoutPages()
2770 // called by: notifySetup, viewportResizeEvent, slotViewMode, slotContinuousToggled, updateZoom
2772 // set an empty container if we have no pages
2773 int pageCount = d->items.count();
2774 if ( pageCount < 1 )
2776 setWidgetResizable(true);
2777 return;
2780 // if viewport was auto-moving, stop it
2781 if ( d->viewportMoveActive )
2783 center( d->viewportMoveDest.x(), d->viewportMoveDest.y() );
2784 d->viewportMoveActive = false;
2785 d->viewportMoveTimer->stop();
2786 verticalScrollBar()->setEnabled( true );
2787 horizontalScrollBar()->setEnabled( true );
2790 // common iterator used in this method and viewport parameters
2791 QVector< PageViewItem * >::const_iterator iIt, iEnd = d->items.end();
2792 int viewportWidth = viewport()->width(),
2793 viewportHeight = viewport()->height(),
2794 fullWidth = 0,
2795 fullHeight = 0;
2796 QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewportWidth, viewportHeight );
2798 // handle the 'center first page in row' stuff
2799 int nCols = viewColumns();
2800 bool centerFirstPage = Okular::Settings::centerFirstPageInRow() && nCols > 1;
2801 const bool continuousView = Okular::Settings::viewContinuous();
2803 // set all items geometry and resize contents. handle 'continuous' and 'single' modes separately
2805 PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage() ) ];
2807 // handle the 'centering on first row' stuff
2808 if ( centerFirstPage )
2809 pageCount += nCols - 1;
2810 // Here we find out column's width and row's height to compute a table
2811 // so we can place widgets 'centered in virtual cells'.
2812 int nRows;
2814 // if ( Okular::Settings::viewMode() < 2 )
2815 nRows = (int)ceil( (float)pageCount / (float)nCols );
2816 // nRows=(int)ceil( (float)pageCount / (float) Okular::Settings::viewRows() );
2817 // else
2818 // nRows = Okular::Settings::viewRows();
2820 int * colWidth = new int[ nCols ],
2821 * rowHeight = new int[ nRows ],
2822 cIdx = 0,
2823 rIdx = 0;
2824 for ( int i = 0; i < nCols; i++ )
2825 colWidth[ i ] = viewportWidth / nCols;
2826 for ( int i = 0; i < nRows; i++ )
2827 rowHeight[ i ] = 0;
2828 // handle the 'centering on first row' stuff
2829 if ( centerFirstPage )
2831 pageCount -= nCols - 1;
2832 cIdx += nCols - 1;
2835 // 1) find the maximum columns width and rows height for a grid in
2836 // which each page must well-fit inside a cell
2837 for ( iIt = d->items.begin(); iIt != iEnd; ++iIt )
2839 PageViewItem * item = *iIt;
2840 // update internal page size (leaving a little margin in case of Fit* modes)
2841 updateItemSize( item, colWidth[ cIdx ] - 6, viewportHeight - 12 );
2842 // find row's maximum height and column's max width
2843 if ( item->croppedWidth() + 6 > colWidth[ cIdx ] )
2844 colWidth[ cIdx ] = item->croppedWidth() + 6;
2845 if ( item->croppedHeight() + 12 > rowHeight[ rIdx ] )
2846 rowHeight[ rIdx ] = item->croppedHeight() + 12;
2847 // handle the 'centering on first row' stuff
2848 // update col/row indices
2849 if ( ++cIdx == nCols )
2851 cIdx = 0;
2852 rIdx++;
2856 const int pageRowIdx = ( ( centerFirstPage ? nCols - 1 : 0 ) + currentItem->pageNumber() ) / nCols;
2858 // 2) compute full size
2859 for ( int i = 0; i < nCols; i++ )
2860 fullWidth += colWidth[ i ];
2861 if ( continuousView )
2863 for ( int i = 0; i < nRows; i++ )
2864 fullHeight += rowHeight[ i ];
2866 else
2867 fullHeight = rowHeight[ pageRowIdx ];
2869 if (!horizontalScrollBar()->isVisible() && !verticalScrollBar()->isVisible() && fullWidth == viewportWidth &&
2870 fullHeight - viewportHeight == 1 && d->zoomMode == ZoomFitWidth)
2872 // this saves us from infinite resizing loop because of scrollbars appearing and disappearing
2873 // see bug 160628
2874 fullHeight = viewportHeight;
2877 // 3) arrange widgets inside cells (and refine fullHeight if needed)
2878 int insertX = 0,
2879 insertY = fullHeight < viewportHeight ? ( viewportHeight - fullHeight ) / 2 : 0;
2880 const int origInsertY = insertY;
2881 cIdx = 0;
2882 rIdx = 0;
2883 if ( centerFirstPage )
2885 cIdx += nCols - 1;
2886 for ( int i = 0; i < cIdx; ++i )
2887 insertX += colWidth[ i ];
2889 for ( iIt = d->items.begin(); iIt != iEnd; ++iIt )
2891 PageViewItem * item = *iIt;
2892 int cWidth = colWidth[ cIdx ],
2893 rHeight = rowHeight[ rIdx ];
2894 if ( continuousView || rIdx == pageRowIdx )
2896 const bool reallyDoCenterFirst = item->pageNumber() == 0 && centerFirstPage;
2897 item->moveTo( (reallyDoCenterFirst ? 0 : insertX) + ( (reallyDoCenterFirst ? fullWidth : cWidth) - item->croppedWidth()) / 2,
2898 (continuousView ? insertY : origInsertY) + (rHeight - item->croppedHeight()) / 2 );
2899 item->setVisible( true );
2901 else
2903 item->moveTo( 0, 0 );
2904 item->setVisible( false );
2906 item->setFormWidgetsVisible( d->m_formsVisible );
2907 // advance col/row index
2908 insertX += cWidth;
2909 if ( ++cIdx == nCols )
2911 cIdx = 0;
2912 rIdx++;
2913 insertX = 0;
2914 insertY += rHeight;
2916 #ifdef PAGEVIEW_DEBUG
2917 kWarning() << "updating size for pageno" << item->pageNumber() << "cropped" << item->croppedGeometry() << "uncropped" << item->uncroppedGeometry();
2918 #endif
2921 delete [] colWidth;
2922 delete [] rowHeight;
2924 // 3) reset dirty state
2925 d->dirtyLayout = false;
2927 horizontalScrollBar()->setRange( 0, qMax( 0, fullWidth - viewport()->width() ) );
2928 verticalScrollBar()->setRange( 0, qMax( 0, fullHeight - viewport()->height() ) );
2930 // 4) update scrollview's contents size and recenter view
2931 bool wasUpdatesEnabled = viewport()->updatesEnabled();
2932 if ( fullWidth != widget()->width() || fullHeight != widget()->height() )
2934 // disable updates and resize the viewportContents
2935 if ( wasUpdatesEnabled )
2936 viewport()->setUpdatesEnabled( false );
2937 setWidgetResizable(false);
2938 fullWidth = qMax(fullWidth, viewport()->width());
2939 fullHeight = qMax(fullHeight, viewport()->height());
2940 widget()->resize( fullWidth, fullHeight );
2941 // restore previous viewport if defined and updates enabled
2942 if ( wasUpdatesEnabled )
2944 const Okular::DocumentViewport & vp = d->document->viewport();
2945 if ( vp.pageNumber >= 0 )
2947 int prevX = horizontalScrollBar()->value(),
2948 prevY = verticalScrollBar()->value();
2949 const QRect & geometry = d->items[ vp.pageNumber ]->croppedGeometry();
2950 double nX = vp.rePos.enabled ? vp.rePos.normalizedX : 0.5,
2951 nY = vp.rePos.enabled ? vp.rePos.normalizedY : 0.0;
2952 center( geometry.left() + qRound( nX * (double)geometry.width() ),
2953 geometry.top() + qRound( nY * (double)geometry.height() ) );
2954 // center() usually moves the viewport, that requests pixmaps too.
2955 // if that doesn't happen we have to request them by hand
2956 if ( prevX == horizontalScrollBar()->value() && prevY == verticalScrollBar()->value() )
2957 slotRequestVisiblePixmaps();
2959 // or else go to center page
2960 else
2961 center( fullWidth / 2, 0 );
2962 viewport()->setUpdatesEnabled( true );
2966 // 5) update the whole viewport if updated enabled
2967 if ( wasUpdatesEnabled )
2968 widget()->update();
2971 void PageView::slotRequestVisiblePixmaps( int newValue )
2973 // if requests are blocked (because raised by an unwanted event), exit
2974 if ( d->blockPixmapsRequest || d->viewportMoveActive ||
2975 d->mouseMidZooming )
2976 return;
2978 // precalc view limits for intersecting with page coords inside the lOOp
2979 bool isEvent = newValue != -1 && !d->blockViewport;
2980 QRect viewportRect( horizontalScrollBar()->value(),
2981 verticalScrollBar()->value(),
2982 viewport()->width(), viewport()->height() );
2984 // some variables used to determine the viewport
2985 int nearPageNumber = -1;
2986 double viewportCenterX = (viewportRect.left() + viewportRect.right()) / 2.0,
2987 viewportCenterY = (viewportRect.top() + viewportRect.bottom()) / 2.0,
2988 focusedX = 0.5,
2989 focusedY = 0.0,
2990 minDistance = -1.0;
2992 // iterate over all items
2993 d->visibleItems.clear();
2994 QLinkedList< Okular::PixmapRequest * > requestedPixmaps;
2995 QVector< Okular::VisiblePageRect * > visibleRects;
2996 QVector< PageViewItem * >::const_iterator iIt = d->items.begin(), iEnd = d->items.end();
2997 for ( ; iIt != iEnd; ++iIt )
2999 PageViewItem * i = *iIt;
3000 if ( !i->isVisible() )
3001 continue;
3002 #ifdef PAGEVIEW_DEBUG
3003 kWarning() << "checking page" << i->pageNumber();
3004 kWarning().nospace() << "viewportRect is " << viewportRect << ", page item is " << i->croppedGeometry() << " intersect : " << viewportRect.intersects( i->croppedGeometry() );
3005 #endif
3006 // if the item doesn't intersect the viewport, skip it
3007 QRect intersectionRect = viewportRect.intersect( i->croppedGeometry() );
3008 if ( intersectionRect.isEmpty() )
3009 continue;
3011 // add the item to the 'visible list'
3012 d->visibleItems.push_back( i );
3013 Okular::VisiblePageRect * vItem = new Okular::VisiblePageRect( i->pageNumber(), Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ) );
3014 visibleRects.push_back( vItem );
3015 #ifdef PAGEVIEW_DEBUG
3016 kWarning() << "checking for pixmap for page" << i->pageNumber() << "=" << i->page()->hasPixmap( PAGEVIEW_ID, i->uncroppedWidth(), i->uncroppedHeight() );
3017 kWarning() << "checking for text for page" << i->pageNumber() << "=" << i->page()->hasTextPage();
3018 #endif
3019 // if the item has not the right pixmap, add a request for it
3020 // TODO: We presently request a pixmap for the full page, and then render just the crop part. This waste memory and cycles.
3021 if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->uncroppedWidth(), i->uncroppedHeight() ) )
3023 #ifdef PAGEVIEW_DEBUG
3024 kWarning() << "rerequesting visible pixmaps for page" << i->pageNumber() << "!";
3025 #endif
3026 Okular::PixmapRequest * p = new Okular::PixmapRequest(
3027 PAGEVIEW_ID, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRIO, true );
3028 requestedPixmaps.push_back( p );
3031 // look for the item closest to viewport center and the relative
3032 // position between the item and the viewport center
3033 if ( isEvent )
3035 const QRect & geometry = i->croppedGeometry();
3036 // compute distance between item center and viewport center (slightly moved left)
3037 double distance = hypot( (geometry.left() + geometry.right()) / 2 - (viewportCenterX - 4),
3038 (geometry.top() + geometry.bottom()) / 2 - viewportCenterY );
3039 if ( distance >= minDistance && nearPageNumber != -1 )
3040 continue;
3041 nearPageNumber = i->pageNumber();
3042 minDistance = distance;
3043 if ( geometry.height() > 0 && geometry.width() > 0 )
3045 focusedX = ( viewportCenterX - (double)geometry.left() ) / (double)geometry.width();
3046 focusedY = ( viewportCenterY - (double)geometry.top() ) / (double)geometry.height();
3051 // if preloading is enabled, add the pages before and after in preloading
3052 if ( !d->visibleItems.isEmpty() &&
3053 Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Low &&
3054 Okular::Settings::enableThreading() )
3056 // as the requests are done in the order as they appear in the list,
3057 // request first the next page and then the previous
3059 // add the page after the 'visible series' in preload
3060 int tailRequest = d->visibleItems.last()->pageNumber() + 1;
3061 if ( tailRequest < (int)d->items.count() )
3063 PageViewItem * i = d->items[ tailRequest ];
3064 // request the pixmap if not already present
3065 if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->uncroppedWidth(), i->uncroppedHeight() ) && i->uncroppedWidth() > 0 )
3066 requestedPixmaps.push_back( new Okular::PixmapRequest(
3067 PAGEVIEW_ID, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, true ) );
3070 // add the page before the 'visible series' in preload
3071 int headRequest = d->visibleItems.first()->pageNumber() - 1;
3072 if ( headRequest >= 0 )
3074 PageViewItem * i = d->items[ headRequest ];
3075 // request the pixmap if not already present
3076 if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->uncroppedWidth(), i->uncroppedHeight() ) && i->uncroppedWidth() > 0 )
3077 requestedPixmaps.push_back( new Okular::PixmapRequest(
3078 PAGEVIEW_ID, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, true ) );
3082 // send requests to the document
3083 if ( !requestedPixmaps.isEmpty() )
3085 d->document->requestPixmaps( requestedPixmaps );
3087 // if this functions was invoked by viewport events, send update to document
3088 if ( isEvent && nearPageNumber != -1 )
3090 // determine the document viewport
3091 Okular::DocumentViewport newViewport( nearPageNumber );
3092 newViewport.rePos.enabled = true;
3093 newViewport.rePos.normalizedX = focusedX;
3094 newViewport.rePos.normalizedY = focusedY;
3095 // set the viewport to other observers
3096 d->document->setViewport( newViewport , PAGEVIEW_ID);
3098 d->document->setVisiblePageRects( visibleRects, PAGEVIEW_ID );
3101 void PageView::slotMoveViewport()
3103 // converge to viewportMoveDest in 1 second
3104 int diffTime = d->viewportMoveTime.elapsed();
3105 if ( diffTime >= 667 || !d->viewportMoveActive )
3107 center( d->viewportMoveDest.x(), d->viewportMoveDest.y() );
3108 d->viewportMoveTimer->stop();
3109 d->viewportMoveActive = false;
3110 slotRequestVisiblePixmaps();
3111 verticalScrollBar()->setEnabled( true );
3112 horizontalScrollBar()->setEnabled( true );
3113 return;
3116 // move the viewport smoothly (kmplot: p(x)=1+0.47*(x-1)^3-0.25*(x-1)^4)
3117 float convergeSpeed = (float)diffTime / 667.0,
3118 x = ((float)viewport()->width() / 2.0) + horizontalScrollBar()->value(),
3119 y = ((float)viewport()->height() / 2.0) + verticalScrollBar()->value(),
3120 diffX = (float)d->viewportMoveDest.x() - x,
3121 diffY = (float)d->viewportMoveDest.y() - y;
3122 convergeSpeed *= convergeSpeed * (1.4 - convergeSpeed);
3123 center( (int)(x + diffX * convergeSpeed),
3124 (int)(y + diffY * convergeSpeed ) );
3127 void PageView::slotAutoScoll()
3129 // the first time create the timer
3130 if ( !d->autoScrollTimer )
3132 d->autoScrollTimer = new QTimer( this );
3133 d->autoScrollTimer->setSingleShot( true );
3134 connect( d->autoScrollTimer, SIGNAL( timeout() ), this, SLOT( slotAutoScoll() ) );
3137 // if scrollIncrement is zero, stop the timer
3138 if ( !d->scrollIncrement )
3140 d->autoScrollTimer->stop();
3141 return;
3144 // compute delay between timer ticks and scroll amount per tick
3145 int index = abs( d->scrollIncrement ) - 1; // 0..9
3146 const int scrollDelay[10] = { 200, 100, 50, 30, 20, 30, 25, 20, 30, 20 };
3147 const int scrollOffset[10] = { 1, 1, 1, 1, 1, 2, 2, 2, 4, 4 };
3148 d->autoScrollTimer->start( scrollDelay[ index ] );
3149 int delta = d->scrollIncrement > 0 ? scrollOffset[ index ] : -scrollOffset[ index ];
3150 verticalScrollBar()->setValue(verticalScrollBar()->value() + delta);
3153 void PageView::slotDragScroll()
3155 horizontalScrollBar()->setValue(horizontalScrollBar()->value() + d->dragScrollVector.x());
3156 verticalScrollBar()->setValue(verticalScrollBar()->value() + d->dragScrollVector.y());
3157 QPoint p = widget()->mapFromGlobal( QCursor::pos() );
3158 selectionEndPoint( p );
3161 void PageView::slotShowWelcome()
3163 // show initial welcome text
3164 d->messageWindow->display( i18n( "Welcome" ), PageViewMessage::Info, 2000 );
3167 void PageView::slotZoom()
3169 setFocus();
3170 updateZoom( ZoomFixed );
3173 void PageView::slotZoomIn()
3175 updateZoom( ZoomIn );
3178 void PageView::slotZoomOut()
3180 updateZoom( ZoomOut );
3183 void PageView::slotFitToWidthToggled( bool on )
3185 if ( on ) updateZoom( ZoomFitWidth );
3188 void PageView::slotFitToPageToggled( bool on )
3190 if ( on ) updateZoom( ZoomFitPage );
3193 void PageView::slotFitToTextToggled( bool on )
3195 if ( on ) updateZoom( ZoomFitText );
3198 void PageView::slotViewMode( int nr )
3200 if ( (int)Okular::Settings::viewMode() != nr )
3202 Okular::Settings::setViewMode( nr );
3203 Okular::Settings::self()->writeConfig();
3204 if ( d->document->pages() > 0 )
3205 slotRelayoutPages();
3209 void PageView::slotContinuousToggled( bool on )
3211 if ( Okular::Settings::viewContinuous() != on )
3213 Okular::Settings::setViewContinuous( on );
3214 Okular::Settings::self()->writeConfig();
3215 if ( d->document->pages() > 0 )
3216 slotRelayoutPages();
3220 void PageView::slotSetMouseNormal()
3222 d->mouseMode = MouseNormal;
3223 // hide the messageWindow
3224 d->messageWindow->hide();
3225 // reshow the annotator toolbar if hiding was forced
3226 if ( d->aToggleAnnotator->isChecked() )
3227 slotToggleAnnotator( true );
3228 // force an update of the cursor
3229 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
3232 void PageView::slotSetMouseZoom()
3234 d->mouseMode = MouseZoom;
3235 // change the text in messageWindow (and show it if hidden)
3236 d->messageWindow->display( i18n( "Select zooming area. Right-click to zoom out." ), PageViewMessage::Info, -1 );
3237 // force hiding of annotator toolbar
3238 if ( d->annotator )
3239 d->annotator->setEnabled( false );
3240 // force an update of the cursor
3241 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
3244 void PageView::slotSetMouseSelect()
3246 d->mouseMode = MouseSelect;
3247 // change the text in messageWindow (and show it if hidden)
3248 d->messageWindow->display( i18n( "Draw a rectangle around the text/graphics to copy." ), PageViewMessage::Info, -1 );
3249 // force hiding of annotator toolbar
3250 if ( d->annotator )
3251 d->annotator->setEnabled( false );
3252 // force an update of the cursor
3253 updateCursor( widget()->mapFromGlobal( QCursor::pos() ) );
3256 void PageView::slotSetMouseTextSelect()
3258 d->mouseMode = MouseTextSelect;
3259 // change the text in messageWindow (and show it if hidden)
3260 d->messageWindow->display( i18n( "Select text" ), 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::slotToggleAnnotator( bool on )
3270 // the 'inHere' trick is needed as the slotSetMouseZoom() calls this
3271 static bool inHere = false;
3272 if ( inHere )
3273 return;
3274 inHere = true;
3276 // the annotator can be used in normal mouse mode only, so if asked for it,
3277 // switch to normal mode
3278 if ( on && d->mouseMode != MouseNormal )
3279 d->aMouseNormal->trigger();
3281 // create the annotator object if not present
3282 if ( !d->annotator )
3284 d->annotator = new PageViewAnnotator( this, d->document );
3285 bool allowTools = d->document->pages() > 0 && d->document->isAllowed( Okular::AllowNotes );
3286 d->annotator->setToolsEnabled( allowTools );
3287 d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() );
3290 // initialize/reset annotator (and show/hide toolbar)
3291 d->annotator->setEnabled( on );
3293 inHere = false;
3296 void PageView::slotScrollUp()
3298 if ( d->scrollIncrement < -9 )
3299 return;
3300 d->scrollIncrement--;
3301 slotAutoScoll();
3302 setFocus();
3305 void PageView::slotScrollDown()
3307 if ( d->scrollIncrement > 9 )
3308 return;
3309 d->scrollIncrement++;
3310 slotAutoScoll();
3311 setFocus();
3314 void PageView::slotRotateClockwise()
3316 int id = ( (int)d->document->rotation() + 1 ) % 4;
3317 d->document->setRotation( id );
3320 void PageView::slotRotateCounterClockwise()
3322 int id = ( (int)d->document->rotation() + 3 ) % 4;
3323 d->document->setRotation( id );
3326 void PageView::slotRotateOriginal()
3328 d->document->setRotation( 0 );
3331 void PageView::slotPageSizes( int newsize )
3333 if ( newsize < 0 || newsize >= d->document->pageSizes().count() )
3334 return;
3336 d->document->setPageSize( d->document->pageSizes().at( newsize ) );
3339 void PageView::slotTrimMarginsToggled( bool on )
3341 if ( Okular::Settings::trimMargins() != on )
3343 Okular::Settings::setTrimMargins( on );
3344 Okular::Settings::self()->writeConfig();
3345 if ( d->document->pages() > 0 )
3347 slotRelayoutPages();
3348 slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already!
3353 void PageView::slotToggleForms()
3355 toggleFormWidgets( !d->m_formsVisible );
3358 void PageView::slotFormWidgetChanged( FormWidgetIface *w )
3360 if ( !d->refreshTimer )
3362 d->refreshTimer = new QTimer( this );
3363 d->refreshTimer->setSingleShot( true );
3364 connect( d->refreshTimer, SIGNAL( timeout() ),
3365 this, SLOT( slotRefreshPage() ) );
3367 d->refreshPage = w->pageItem()->pageNumber();
3368 d->refreshTimer->start( 1000 );
3371 void PageView::slotRefreshPage()
3373 const int req = d->refreshPage;
3374 if ( req < 0 )
3375 return;
3376 d->refreshPage = -1;
3377 QMetaObject::invokeMethod( d->document, "refreshPixmaps", Qt::QueuedConnection,
3378 Q_ARG( int, req ) );
3381 void PageView::slotSpeakDocument()
3383 QString text;
3384 QVector< PageViewItem * >::const_iterator it = d->items.begin(), itEnd = d->items.end();
3385 for ( ; it < itEnd; ++it )
3387 Okular::RegularAreaRect * area = textSelectionForItem( *it );
3388 text.append( (*it)->page()->text( area ) );
3389 text.append( '\n' );
3390 delete area;
3393 d->tts()->say( text );
3396 void PageView::slotSpeakCurrentPage()
3398 const int currentPage = d->document->viewport().pageNumber;
3400 PageViewItem *item = d->items.at( currentPage );
3401 Okular::RegularAreaRect * area = textSelectionForItem( item );
3402 const QString text = item->page()->text( area );
3403 delete area;
3405 d->tts()->say( text );
3408 void PageView::slotStopSpeaks()
3410 if ( !d->m_tts )
3411 return;
3413 d->m_tts->stopAllSpeechs();
3416 void PageView::slotAction( Okular::Action *action )
3418 d->document->processAction( action );
3420 //END private SLOTS
3422 #include "pageview.moc"
3424 /* kate: replace-tabs on; indent-width 4; */