You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5050 lines
198 KiB
5050 lines
198 KiB
/*************************************************************************** |
|
* Copyright (C) 2004-2005 by Enrico Ros <eros.kde@email.it> * |
|
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> * |
|
* * |
|
* With portions of code from kpdf/kpdf_pagewidget.cc by: * |
|
* Copyright (C) 2002 by Wilco Greven <greven@kde.org> * |
|
* Copyright (C) 2003 by Christophe Devriese * |
|
* <Christophe.Devriese@student.kuleuven.ac.be> * |
|
* Copyright (C) 2003 by Laurent Montel <montel@kde.org> * |
|
* Copyright (C) 2003 by Dirk Mueller <mueller@kde.org> * |
|
* Copyright (C) 2004 by James Ots <kde@jamesots.com> * |
|
* Copyright (C) 2011 by Jiri Baum - NICTA <jiri@baum.com.au> * |
|
* * |
|
* This program is free software; you can redistribute it and/or modify * |
|
* it under the terms of the GNU General Public License as published by * |
|
* the Free Software Foundation; either version 2 of the License, or * |
|
* (at your option) any later version. * |
|
***************************************************************************/ |
|
|
|
#include "pageview.h" |
|
|
|
// qt/kde includes |
|
#include <qcursor.h> |
|
#include <qevent.h> |
|
#include <qimage.h> |
|
#include <qpainter.h> |
|
#include <qtimer.h> |
|
#include <qset.h> |
|
#include <qscrollbar.h> |
|
#include <qtooltip.h> |
|
#include <qapplication.h> |
|
#include <qclipboard.h> |
|
|
|
#include <kaction.h> |
|
#include <kactionmenu.h> |
|
#include <kstandardaction.h> |
|
#include <kactioncollection.h> |
|
#include <kmenu.h> |
|
#include <klocale.h> |
|
#include <kfiledialog.h> |
|
#include <kglobal.h> |
|
#include <kglobalsettings.h> |
|
#include <kinputdialog.h> |
|
#include <kselectaction.h> |
|
#include <ktoggleaction.h> |
|
#include <kdebug.h> |
|
#include <kdeversion.h> |
|
#include <kmessagebox.h> |
|
#include <kicon.h> |
|
#include <kurifilter.h> |
|
#include <kstringhandler.h> |
|
#include <ktoolinvocation.h> |
|
#include <krun.h> |
|
|
|
// system includes |
|
#include <math.h> |
|
#include <stdlib.h> |
|
|
|
// local includes |
|
#include "formwidgets.h" |
|
#include "pageviewutils.h" |
|
#include "pagepainter.h" |
|
#include "core/annotations.h" |
|
#include "annotwindow.h" |
|
#include "guiutils.h" |
|
#include "annotationpopup.h" |
|
#include "pageviewannotator.h" |
|
#include "priorities.h" |
|
#include "toolaction.h" |
|
#include "tts.h" |
|
#include "videowidget.h" |
|
#include "core/action.h" |
|
#include "core/area.h" |
|
#include "core/document_p.h" |
|
#include "core/form.h" |
|
#include "core/page.h" |
|
#include "core/misc.h" |
|
#include "core/generator.h" |
|
#include "core/movie.h" |
|
#include "core/audioplayer.h" |
|
#include "core/sourcereference.h" |
|
#include "core/tile.h" |
|
#include "settings.h" |
|
#include "settings_core.h" |
|
#include "url_utils.h" |
|
#include "magnifierview.h" |
|
|
|
static int pageflags = PagePainter::Accessibility | PagePainter::EnhanceLinks | |
|
PagePainter::EnhanceImages | PagePainter::Highlights | |
|
PagePainter::TextSelection | PagePainter::Annotations; |
|
|
|
static const float kZoomValues[] = { 0.12, 0.25, 0.33, 0.50, 0.66, 0.75, 1.00, 1.25, 1.50, 2.00, 4.00, 8.00, 16.00 }; |
|
|
|
static inline double normClamp( double value, double def ) |
|
{ |
|
return ( value < 0.0 || value > 1.0 ) ? def : value; |
|
} |
|
|
|
struct TableSelectionPart { |
|
PageViewItem * item; |
|
Okular::NormalizedRect rectInItem; |
|
Okular::NormalizedRect rectInSelection; |
|
|
|
TableSelectionPart(PageViewItem * item_p, const Okular::NormalizedRect &rectInItem_p, const Okular::NormalizedRect &rectInSelection_p); |
|
}; |
|
|
|
TableSelectionPart::TableSelectionPart( PageViewItem * item_p, const Okular::NormalizedRect &rectInItem_p, const Okular::NormalizedRect &rectInSelection_p) |
|
: item ( item_p ), rectInItem (rectInItem_p), rectInSelection (rectInSelection_p) |
|
{ |
|
} |
|
|
|
// structure used internally by PageView for data storage |
|
class PageViewPrivate |
|
{ |
|
public: |
|
PageViewPrivate( PageView *qq ); |
|
|
|
FormWidgetsController* formWidgetsController(); |
|
OkularTTS* tts(); |
|
QString selectedText() const; |
|
|
|
// the document, pageviewItems and the 'visible cache' |
|
PageView *q; |
|
Okular::Document * document; |
|
QVector< PageViewItem * > items; |
|
QLinkedList< PageViewItem * > visibleItems; |
|
MagnifierView *magnifierView; |
|
|
|
// view layout (columns and continuous in Settings), zoom and mouse |
|
PageView::ZoomMode zoomMode; |
|
float zoomFactor; |
|
QPoint mouseGrabPos; |
|
QPoint mousePressPos; |
|
QPoint mouseSelectPos; |
|
int mouseMidLastY; |
|
bool mouseSelecting; |
|
QRect mouseSelectionRect; |
|
QColor mouseSelectionColor; |
|
bool mouseTextSelecting; |
|
QSet< int > pagesWithTextSelection; |
|
bool mouseOnRect; |
|
Okular::Annotation * mouseAnn; |
|
QPoint mouseAnnPos; |
|
int mouseAnnPageNum; |
|
|
|
// table selection |
|
QList<double> tableSelectionCols; |
|
QList<double> tableSelectionRows; |
|
QList<TableSelectionPart> tableSelectionParts; |
|
bool tableDividersGuessed; |
|
|
|
// viewport move |
|
bool viewportMoveActive; |
|
QTime viewportMoveTime; |
|
QPoint viewportMoveDest; |
|
int lastSourceLocationViewportPageNumber; |
|
double lastSourceLocationViewportNormalizedX; |
|
double lastSourceLocationViewportNormalizedY; |
|
QTimer * viewportMoveTimer; |
|
// auto scroll |
|
int scrollIncrement; |
|
QTimer * autoScrollTimer; |
|
// annotations |
|
PageViewAnnotator * annotator; |
|
//text annotation dialogs list |
|
QHash< Okular::Annotation *, AnnotWindow * > m_annowindows; |
|
// other stuff |
|
QTimer * delayResizeEventTimer; |
|
bool dirtyLayout; |
|
bool blockViewport; // prevents changes to viewport |
|
bool blockPixmapsRequest; // prevent pixmap requests |
|
PageViewMessage * messageWindow; // in pageviewutils.h |
|
bool m_formsVisible; |
|
FormWidgetsController *formsWidgetController; |
|
OkularTTS * m_tts; |
|
QTimer * refreshTimer; |
|
int refreshPage; |
|
|
|
// infinite resizing loop prevention |
|
bool verticalScrollBarVisible; |
|
bool horizontalScrollBarVisible; |
|
|
|
// drag scroll |
|
QPoint dragScrollVector; |
|
QTimer dragScrollTimer; |
|
|
|
// left click depress |
|
QTimer leftClickTimer; |
|
|
|
// actions |
|
KAction * aRotateClockwise; |
|
KAction * aRotateCounterClockwise; |
|
KAction * aRotateOriginal; |
|
KSelectAction * aPageSizes; |
|
KToggleAction * aTrimMargins; |
|
KAction * aMouseNormal; |
|
KAction * aMouseSelect; |
|
KAction * aMouseTextSelect; |
|
KAction * aMouseTableSelect; |
|
KAction * aMouseMagnifier; |
|
KToggleAction * aToggleAnnotator; |
|
KSelectAction * aZoom; |
|
KAction * aZoomIn; |
|
KAction * aZoomOut; |
|
KToggleAction * aZoomFitWidth; |
|
KToggleAction * aZoomFitPage; |
|
KToggleAction * aZoomAutoFit; |
|
KActionMenu * aViewMode; |
|
KToggleAction * aViewContinuous; |
|
QAction * aPrevAction; |
|
KAction * aToggleForms; |
|
KAction * aSpeakDoc; |
|
KAction * aSpeakPage; |
|
KAction * aSpeakStop; |
|
KActionCollection * actionCollection; |
|
QActionGroup * mouseModeActionGroup; |
|
|
|
int setting_viewCols; |
|
|
|
// Keep track of whether tablet pen is currently pressed down |
|
bool penDown; |
|
}; |
|
|
|
PageViewPrivate::PageViewPrivate( PageView *qq ) |
|
: q( qq ) |
|
{ |
|
} |
|
|
|
FormWidgetsController* PageViewPrivate::formWidgetsController() |
|
{ |
|
if ( !formsWidgetController ) |
|
{ |
|
formsWidgetController = new FormWidgetsController( document ); |
|
QObject::connect( formsWidgetController, SIGNAL( changed( int ) ), |
|
q, SLOT( slotFormChanged( int ) ) ); |
|
QObject::connect( formsWidgetController, SIGNAL( action( Okular::Action* ) ), |
|
q, SLOT( slotAction( Okular::Action* ) ) ); |
|
} |
|
|
|
return formsWidgetController; |
|
} |
|
|
|
OkularTTS* PageViewPrivate::tts() |
|
{ |
|
if ( !m_tts ) |
|
{ |
|
m_tts = new OkularTTS( q ); |
|
if ( aSpeakStop ) |
|
{ |
|
QObject::connect( m_tts, SIGNAL(hasSpeechs(bool)), |
|
aSpeakStop, SLOT(setEnabled(bool)) ); |
|
QObject::connect( m_tts, SIGNAL(errorMessage(QString)), |
|
q, SLOT(errorMessage(QString)) ); |
|
} |
|
} |
|
|
|
return m_tts; |
|
} |
|
|
|
|
|
/* PageView. What's in this file? -> quick overview. |
|
* Code weight (in rows) and meaning: |
|
* 160 - constructor and creating actions plus their connected slots (empty stuff) |
|
* 70 - DocumentObserver inherited methodes (important) |
|
* 550 - events: mouse, keyboard, drag |
|
* 170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes |
|
* 100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc.. |
|
* other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable, |
|
* and many insignificant stuff like this comment :-) |
|
*/ |
|
PageView::PageView( QWidget *parent, Okular::Document *document ) |
|
: QAbstractScrollArea( parent ) |
|
, Okular::View( QString::fromLatin1( "PageView" ) ) |
|
{ |
|
// create and initialize private storage structure |
|
d = new PageViewPrivate( this ); |
|
d->document = document; |
|
d->aRotateClockwise = 0; |
|
d->aRotateCounterClockwise = 0; |
|
d->aRotateOriginal = 0; |
|
d->aViewMode = 0; |
|
d->zoomMode = PageView::ZoomFitWidth; |
|
d->zoomFactor = 1.0; |
|
d->mouseSelecting = false; |
|
d->mouseTextSelecting = false; |
|
d->mouseOnRect = false; |
|
d->mouseAnn = 0; |
|
d->tableDividersGuessed = false; |
|
d->viewportMoveActive = false; |
|
d->lastSourceLocationViewportPageNumber = -1; |
|
d->lastSourceLocationViewportNormalizedX = 0.0; |
|
d->lastSourceLocationViewportNormalizedY = 0.0; |
|
d->viewportMoveTimer = 0; |
|
d->scrollIncrement = 0; |
|
d->autoScrollTimer = 0; |
|
d->annotator = 0; |
|
d->dirtyLayout = false; |
|
d->blockViewport = false; |
|
d->blockPixmapsRequest = false; |
|
d->messageWindow = new PageViewMessage(this); |
|
d->m_formsVisible = false; |
|
d->formsWidgetController = 0; |
|
d->m_tts = 0; |
|
d->refreshTimer = 0; |
|
d->refreshPage = -1; |
|
d->aRotateClockwise = 0; |
|
d->aRotateCounterClockwise = 0; |
|
d->aRotateOriginal = 0; |
|
d->aPageSizes = 0; |
|
d->aTrimMargins = 0; |
|
d->aMouseNormal = 0; |
|
d->aMouseSelect = 0; |
|
d->aMouseTextSelect = 0; |
|
d->aToggleAnnotator = 0; |
|
d->aZoomFitWidth = 0; |
|
d->aZoomFitPage = 0; |
|
d->aZoomAutoFit = 0; |
|
d->aViewMode = 0; |
|
d->aViewContinuous = 0; |
|
d->aPrevAction = 0; |
|
d->aToggleForms = 0; |
|
d->aSpeakDoc = 0; |
|
d->aSpeakPage = 0; |
|
d->aSpeakStop = 0; |
|
d->actionCollection = 0; |
|
d->aPageSizes=0; |
|
d->setting_viewCols = Okular::Settings::viewColumns(); |
|
d->mouseModeActionGroup = 0; |
|
d->penDown = false; |
|
d->aMouseMagnifier = 0; |
|
|
|
switch( Okular::Settings::zoomMode() ) |
|
{ |
|
case 0: |
|
{ |
|
d->zoomFactor = 1; |
|
d->zoomMode = PageView::ZoomFixed; |
|
break; |
|
} |
|
case 1: |
|
{ |
|
d->zoomMode = PageView::ZoomFitWidth; |
|
break; |
|
} |
|
case 2: |
|
{ |
|
d->zoomMode = PageView::ZoomFitPage; |
|
break; |
|
} |
|
case 3: |
|
{ |
|
d->zoomMode = PageView::ZoomFitAuto; |
|
break; |
|
} |
|
} |
|
|
|
d->delayResizeEventTimer = new QTimer( this ); |
|
d->delayResizeEventTimer->setSingleShot( true ); |
|
connect( d->delayResizeEventTimer, SIGNAL(timeout()), this, SLOT(delayedResizeEvent()) ); |
|
|
|
setFrameStyle(QFrame::NoFrame); |
|
|
|
setAttribute( Qt::WA_StaticContents ); |
|
|
|
setObjectName( QLatin1String( "okular::pageView" ) ); |
|
|
|
// viewport setup: setup focus, and track mouse |
|
viewport()->setFocusProxy( this ); |
|
viewport()->setFocusPolicy( Qt::StrongFocus ); |
|
viewport()->setAttribute( Qt::WA_OpaquePaintEvent ); |
|
viewport()->setAttribute( Qt::WA_NoSystemBackground ); |
|
viewport()->setMouseTracking( true ); |
|
viewport()->setAutoFillBackground( false ); |
|
// the apparently "magic" value of 20 is the same used internally in QScrollArea |
|
verticalScrollBar()->setSingleStep( 20 ); |
|
horizontalScrollBar()->setSingleStep( 20 ); |
|
|
|
// conntect the padding of the viewport to pixmaps requests |
|
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRequestVisiblePixmaps(int))); |
|
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(slotRequestVisiblePixmaps(int))); |
|
connect( &d->dragScrollTimer, SIGNAL(timeout()), this, SLOT(slotDragScroll()) ); |
|
|
|
d->leftClickTimer.setSingleShot( true ); |
|
connect( &d->leftClickTimer, SIGNAL(timeout()), this, SLOT(slotShowSizeAllCursor()) ); |
|
|
|
// set a corner button to resize the view to the page size |
|
// QPushButton * resizeButton = new QPushButton( viewport() ); |
|
// resizeButton->setPixmap( SmallIcon("crop") ); |
|
// setCornerWidget( resizeButton ); |
|
// resizeButton->setEnabled( false ); |
|
// connect(...); |
|
setAttribute( Qt::WA_InputMethodEnabled, true ); |
|
|
|
d->magnifierView = new MagnifierView(document, this); |
|
d->magnifierView->hide(); |
|
d->magnifierView->setGeometry(0, 0, 351, 201); // TODO: more dynamic? |
|
document->addObserver(d->magnifierView); |
|
|
|
connect(document, SIGNAL(processMovieAction(const Okular::MovieAction*)), this, SLOT(slotProcessMovieAction(const Okular::MovieAction*))); |
|
connect(document, SIGNAL(processRenditionAction(const Okular::RenditionAction*)), this, SLOT(slotProcessRenditionAction(const Okular::RenditionAction*))); |
|
|
|
// schedule the welcome message |
|
QMetaObject::invokeMethod(this, "slotShowWelcome", Qt::QueuedConnection); |
|
} |
|
|
|
PageView::~PageView() |
|
{ |
|
if ( d->m_tts ) |
|
d->m_tts->stopAllSpeechs(); |
|
|
|
// delete the local storage structure |
|
|
|
// We need to assign it to a different list otherwise slotAnnotationWindowDestroyed |
|
// will bite us and clear d->m_annowindows |
|
QHash< Okular::Annotation *, AnnotWindow * > annowindows = d->m_annowindows; |
|
d->m_annowindows.clear(); |
|
qDeleteAll( annowindows ); |
|
|
|
// delete all widgets |
|
QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); |
|
for ( ; dIt != dEnd; ++dIt ) |
|
delete *dIt; |
|
delete d->formsWidgetController; |
|
d->document->removeObserver( this ); |
|
delete d; |
|
} |
|
|
|
void PageView::setupBaseActions( KActionCollection * ac ) |
|
{ |
|
d->actionCollection = ac; |
|
|
|
// Zoom actions ( higher scales takes lots of memory! ) |
|
d->aZoom = new KSelectAction(KIcon( "page-zoom" ), i18n("Zoom"), this); |
|
ac->addAction("zoom_to", d->aZoom ); |
|
d->aZoom->setEditable( true ); |
|
d->aZoom->setMaxComboViewCount( 14 ); |
|
connect( d->aZoom, SIGNAL(triggered(QAction*)), this, SLOT(slotZoom()) ); |
|
updateZoomText(); |
|
|
|
d->aZoomIn = KStandardAction::zoomIn( this, SLOT(slotZoomIn()), ac ); |
|
|
|
d->aZoomOut = KStandardAction::zoomOut( this, SLOT(slotZoomOut()), ac ); |
|
} |
|
|
|
void PageView::setupViewerActions( KActionCollection * ac ) |
|
{ |
|
d->actionCollection = ac; |
|
|
|
d->aZoomIn->setShortcut( QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Plus) ); |
|
d->aZoomOut->setShortcut( QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Minus) ); |
|
|
|
// orientation menu actions |
|
d->aRotateClockwise = new KAction( KIcon( "object-rotate-right" ), i18n( "Rotate &Right" ), this ); |
|
d->aRotateClockwise->setIconText( i18nc( "Rotate right", "Right" ) ); |
|
ac->addAction( "view_orientation_rotate_cw", d->aRotateClockwise ); |
|
d->aRotateClockwise->setEnabled( false ); |
|
connect( d->aRotateClockwise, SIGNAL(triggered()), this, SLOT(slotRotateClockwise()) ); |
|
d->aRotateCounterClockwise = new KAction( KIcon( "object-rotate-left" ), i18n( "Rotate &Left" ), this ); |
|
d->aRotateCounterClockwise->setIconText( i18nc( "Rotate left", "Left" ) ); |
|
ac->addAction( "view_orientation_rotate_ccw", d->aRotateCounterClockwise ); |
|
d->aRotateCounterClockwise->setEnabled( false ); |
|
connect( d->aRotateCounterClockwise, SIGNAL(triggered()), this, SLOT(slotRotateCounterClockwise()) ); |
|
d->aRotateOriginal = new KAction( i18n( "Original Orientation" ), this ); |
|
ac->addAction( "view_orientation_original", d->aRotateOriginal ); |
|
d->aRotateOriginal->setEnabled( false ); |
|
connect( d->aRotateOriginal, SIGNAL(triggered()), this, SLOT(slotRotateOriginal()) ); |
|
|
|
d->aPageSizes = new KSelectAction(i18n("&Page Size"), this); |
|
ac->addAction("view_pagesizes", d->aPageSizes); |
|
d->aPageSizes->setEnabled( false ); |
|
|
|
connect( d->aPageSizes , SIGNAL(triggered(int)), |
|
this, SLOT(slotPageSizes(int)) ); |
|
|
|
d->aTrimMargins = new KToggleAction( i18n( "&Trim Margins" ), this ); |
|
ac->addAction("view_trim_margins", d->aTrimMargins ); |
|
connect( d->aTrimMargins, SIGNAL(toggled(bool)), SLOT(slotTrimMarginsToggled(bool)) ); |
|
d->aTrimMargins->setChecked( Okular::Settings::trimMargins() ); |
|
|
|
d->aZoomFitWidth = new KToggleAction(KIcon( "zoom-fit-width" ), i18n("Fit &Width"), this); |
|
ac->addAction("view_fit_to_width", d->aZoomFitWidth ); |
|
connect( d->aZoomFitWidth, SIGNAL(toggled(bool)), SLOT(slotFitToWidthToggled(bool)) ); |
|
|
|
d->aZoomFitPage = new KToggleAction(KIcon( "zoom-fit-best" ), i18n("Fit &Page"), this); |
|
ac->addAction("view_fit_to_page", d->aZoomFitPage ); |
|
connect( d->aZoomFitPage, SIGNAL(toggled(bool)), SLOT(slotFitToPageToggled(bool)) ); |
|
|
|
d->aZoomAutoFit = new KToggleAction(KIcon( "zoom-fit-best" ), i18n("&Auto Fit"), this); |
|
ac->addAction("view_auto_fit", d->aZoomAutoFit ); |
|
connect( d->aZoomAutoFit, SIGNAL(toggled(bool)), SLOT(slotAutoFitToggled(bool)) ); |
|
|
|
// View-Layout actions |
|
d->aViewMode = new KActionMenu( KIcon( "view-split-left-right" ), i18n( "&View Mode" ), this ); |
|
d->aViewMode->setDelayed( false ); |
|
#define ADD_VIEWMODE_ACTION( text, name, id ) \ |
|
do { \ |
|
KAction *vm = new KAction( text, d->aViewMode->menu() ); \ |
|
vm->setCheckable( true ); \ |
|
vm->setData( qVariantFromValue( id ) ); \ |
|
d->aViewMode->addAction( vm ); \ |
|
ac->addAction( name, vm ); \ |
|
vmGroup->addAction( vm ); \ |
|
} while( 0 ) |
|
ac->addAction("view_render_mode", d->aViewMode ); |
|
QActionGroup *vmGroup = new QActionGroup( d->aViewMode->menu() ); |
|
ADD_VIEWMODE_ACTION( i18n( "Single Page" ), "view_render_mode_single", (int)Okular::Settings::EnumViewMode::Single ); |
|
ADD_VIEWMODE_ACTION( i18n( "Facing Pages" ), "view_render_mode_facing", (int)Okular::Settings::EnumViewMode::Facing ); |
|
ADD_VIEWMODE_ACTION( i18n( "Facing Pages (Center First Page)" ), "view_render_mode_facing_center_first", (int)Okular::Settings::EnumViewMode::FacingFirstCentered ); |
|
ADD_VIEWMODE_ACTION( i18n( "Overview" ), "view_render_mode_overview", (int)Okular::Settings::EnumViewMode::Summary ); |
|
const QList<QAction *> viewModeActions = d->aViewMode->menu()->actions(); |
|
foreach(QAction *viewModeAction, viewModeActions) |
|
{ |
|
if (viewModeAction->data().toInt() == Okular::Settings::viewMode()) |
|
{ |
|
viewModeAction->setChecked( true ); |
|
} |
|
} |
|
connect( vmGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotViewMode(QAction*)) ); |
|
#undef ADD_VIEWMODE_ACTION |
|
|
|
d->aViewContinuous = new KToggleAction(KIcon( "view-list-text" ), i18n("&Continuous"), this); |
|
ac->addAction("view_continuous", d->aViewContinuous ); |
|
connect( d->aViewContinuous, SIGNAL(toggled(bool)), SLOT(slotContinuousToggled(bool)) ); |
|
d->aViewContinuous->setChecked( Okular::Settings::viewContinuous() ); |
|
|
|
// Mouse mode actions for viewer mode |
|
d->mouseModeActionGroup = new QActionGroup( this ); |
|
d->mouseModeActionGroup->setExclusive( true ); |
|
d->aMouseNormal = new KAction( KIcon( "input-mouse" ), i18n( "&Browse Tool" ), this ); |
|
ac->addAction("mouse_drag", d->aMouseNormal ); |
|
connect( d->aMouseNormal, SIGNAL(triggered()), this, SLOT(slotSetMouseNormal()) ); |
|
d->aMouseNormal->setIconText( i18nc( "Browse Tool", "Browse" ) ); |
|
d->aMouseNormal->setCheckable( true ); |
|
d->aMouseNormal->setShortcut( Qt::CTRL + Qt::Key_1 ); |
|
d->aMouseNormal->setActionGroup( d->mouseModeActionGroup ); |
|
d->aMouseNormal->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Browse ); |
|
|
|
KAction * mz = new KAction(KIcon( "page-zoom" ), i18n("&Zoom Tool"), this); |
|
ac->addAction("mouse_zoom", mz ); |
|
connect( mz, SIGNAL(triggered()), this, SLOT(slotSetMouseZoom()) ); |
|
mz->setIconText( i18nc( "Zoom Tool", "Zoom" ) ); |
|
mz->setCheckable( true ); |
|
mz->setShortcut( Qt::CTRL + Qt::Key_2 ); |
|
mz->setActionGroup( d->mouseModeActionGroup ); |
|
mz->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Zoom ); |
|
|
|
KAction * aToggleChangeColors = new KAction(i18n("&Toggle Change Colors"), this); |
|
ac->addAction("toggle_change_colors", aToggleChangeColors ); |
|
connect( aToggleChangeColors, SIGNAL(triggered()), this, SLOT(slotToggleChangeColors()) ); |
|
} |
|
|
|
// WARNING: 'setupViewerActions' must have been called before this method |
|
void PageView::setupActions( KActionCollection * ac ) |
|
{ |
|
d->actionCollection = ac; |
|
|
|
d->aZoomIn->setShortcut( KStandardShortcut::zoomIn() ); |
|
d->aZoomOut->setShortcut( KStandardShortcut::zoomOut() ); |
|
|
|
// Mouse-Mode actions |
|
d->aMouseSelect = new KAction(KIcon( "select-rectangular" ), i18n("&Selection Tool"), this); |
|
ac->addAction("mouse_select", d->aMouseSelect ); |
|
connect( d->aMouseSelect, SIGNAL(triggered()), this, SLOT(slotSetMouseSelect()) ); |
|
d->aMouseSelect->setIconText( i18nc( "Select Tool", "Selection" ) ); |
|
d->aMouseSelect->setCheckable( true ); |
|
d->aMouseSelect->setShortcut( Qt::CTRL + Qt::Key_3 ); |
|
d->aMouseSelect->setActionGroup( d->mouseModeActionGroup ); |
|
d->aMouseSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::RectSelect ); |
|
|
|
d->aMouseTextSelect = new KAction(KIcon( "draw-text" ), i18n("&Text Selection Tool"), this); |
|
ac->addAction("mouse_textselect", d->aMouseTextSelect ); |
|
connect( d->aMouseTextSelect, SIGNAL(triggered()), this, SLOT(slotSetMouseTextSelect()) ); |
|
d->aMouseTextSelect->setIconText( i18nc( "Text Selection Tool", "Text Selection" ) ); |
|
d->aMouseTextSelect->setCheckable( true ); |
|
d->aMouseTextSelect->setShortcut( Qt::CTRL + Qt::Key_4 ); |
|
d->aMouseTextSelect->setActionGroup( d->mouseModeActionGroup ); |
|
d->aMouseTextSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TextSelect ); |
|
|
|
d->aMouseTableSelect = new KAction(KIcon( "table" ), i18n("T&able Selection Tool"), this); |
|
ac->addAction("mouse_tableselect", d->aMouseTableSelect ); |
|
connect( d->aMouseTableSelect, SIGNAL( triggered() ), this, SLOT( slotSetMouseTableSelect() ) ); |
|
d->aMouseTableSelect->setIconText( i18nc( "Table Selection Tool", "Table Selection" ) ); |
|
d->aMouseTableSelect->setCheckable( true ); |
|
d->aMouseTableSelect->setShortcut( Qt::CTRL + Qt::Key_5 ); |
|
d->aMouseTableSelect->setActionGroup( d->mouseModeActionGroup ); |
|
d->aMouseTableSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TableSelect ); |
|
|
|
d->aMouseMagnifier = new KAction(KIcon( "document-preview" ), i18n("&Magnifier"), this); |
|
ac->addAction("mouse_magnifier", d->aMouseMagnifier ); |
|
connect( d->aMouseMagnifier, SIGNAL(triggered()), this, SLOT(slotSetMouseMagnifier()) ); |
|
d->aMouseMagnifier->setIconText( i18nc( "Magnifier Tool", "Magnifier" ) ); |
|
d->aMouseMagnifier->setCheckable( true ); |
|
d->aMouseMagnifier->setShortcut( Qt::CTRL + Qt::Key_6 ); |
|
d->aMouseMagnifier->setActionGroup( d->mouseModeActionGroup ); |
|
d->aMouseMagnifier->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Magnifier ); |
|
|
|
d->aToggleAnnotator = new KToggleAction(KIcon( "draw-freehand" ), i18n("&Review"), this); |
|
ac->addAction("mouse_toggle_annotate", d->aToggleAnnotator ); |
|
d->aToggleAnnotator->setCheckable( true ); |
|
connect( d->aToggleAnnotator, SIGNAL(toggled(bool)), SLOT(slotToggleAnnotator(bool)) ); |
|
d->aToggleAnnotator->setShortcut( Qt::Key_F6 ); |
|
|
|
ToolAction *ta = new ToolAction( this ); |
|
ac->addAction( "mouse_selecttools", ta ); |
|
ta->addAction( d->aMouseSelect ); |
|
ta->addAction( d->aMouseTextSelect ); |
|
ta->addAction( d->aMouseTableSelect ); |
|
|
|
// speak actions |
|
d->aSpeakDoc = new KAction( KIcon( "text-speak" ), i18n( "Speak Whole Document" ), this ); |
|
ac->addAction( "speak_document", d->aSpeakDoc ); |
|
d->aSpeakDoc->setEnabled( false ); |
|
connect( d->aSpeakDoc, SIGNAL(triggered()), SLOT(slotSpeakDocument()) ); |
|
|
|
d->aSpeakPage = new KAction( KIcon( "text-speak" ), i18n( "Speak Current Page" ), this ); |
|
ac->addAction( "speak_current_page", d->aSpeakPage ); |
|
d->aSpeakPage->setEnabled( false ); |
|
connect( d->aSpeakPage, SIGNAL(triggered()), SLOT(slotSpeakCurrentPage()) ); |
|
|
|
d->aSpeakStop = new KAction( KIcon( "media-playback-stop" ), i18n( "Stop Speaking" ), this ); |
|
ac->addAction( "speak_stop_all", d->aSpeakStop ); |
|
d->aSpeakStop->setEnabled( false ); |
|
connect( d->aSpeakStop, SIGNAL(triggered()), SLOT(slotStopSpeaks()) ); |
|
|
|
// Other actions |
|
KAction * su = new KAction(i18n("Scroll Up"), this); |
|
ac->addAction("view_scroll_up", su ); |
|
connect( su, SIGNAL(triggered()), this, SLOT(slotAutoScrollUp()) ); |
|
su->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_Up) ); |
|
addAction(su); |
|
|
|
KAction * sd = new KAction(i18n("Scroll Down"), this); |
|
ac->addAction("view_scroll_down", sd ); |
|
connect( sd, SIGNAL(triggered()), this, SLOT(slotAutoScrollDown()) ); |
|
sd->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_Down) ); |
|
addAction(sd); |
|
|
|
KAction * spu = new KAction(i18n("Scroll Page Up"), this); |
|
ac->addAction( "view_scroll_page_up", spu ); |
|
connect( spu, SIGNAL(triggered()), this, SLOT(slotScrollUp()) ); |
|
spu->setShortcut( QKeySequence(Qt::SHIFT + Qt::Key_Space) ); |
|
addAction( spu ); |
|
|
|
KAction * spd = new KAction(i18n("Scroll Page Down"), this); |
|
ac->addAction( "view_scroll_page_down", spd ); |
|
connect( spd, SIGNAL(triggered()), this, SLOT(slotScrollDown()) ); |
|
spd->setShortcut( QKeySequence(Qt::Key_Space) ); |
|
addAction( spd ); |
|
|
|
d->aToggleForms = new KAction( this ); |
|
ac->addAction( "view_toggle_forms", d->aToggleForms ); |
|
connect( d->aToggleForms, SIGNAL(triggered()), this, SLOT(slotToggleForms()) ); |
|
d->aToggleForms->setEnabled( false ); |
|
toggleFormWidgets( false ); |
|
|
|
// Setup undo and redo actions |
|
KAction *kundo = KStandardAction::create( KStandardAction::Undo, d->document, SLOT(undo()), ac ); |
|
KAction *kredo = KStandardAction::create( KStandardAction::Redo, d->document, SLOT(redo()), ac ); |
|
connect(d->document, SIGNAL(canUndoChanged(bool)), kundo, SLOT(setEnabled(bool))); |
|
connect(d->document, SIGNAL(canRedoChanged(bool)), kredo, SLOT(setEnabled(bool))); |
|
kundo->setEnabled(false); |
|
kredo->setEnabled(false); |
|
} |
|
|
|
bool PageView::canFitPageWidth() const |
|
{ |
|
return Okular::Settings::viewMode() != Okular::Settings::EnumViewMode::Single || d->zoomMode != ZoomFitWidth; |
|
} |
|
|
|
void PageView::fitPageWidth( int page ) |
|
{ |
|
// zoom: Fit Width, columns: 1. setActions + relayout + setPage + update |
|
d->zoomMode = ZoomFitWidth; |
|
Okular::Settings::setViewMode( 0 ); |
|
d->aZoomFitWidth->setChecked( true ); |
|
d->aZoomFitPage->setChecked( false ); |
|
d->aZoomAutoFit->setChecked( false ); |
|
d->aViewMode->menu()->actions().at( 0 )->setChecked( true ); |
|
viewport()->setUpdatesEnabled( false ); |
|
slotRelayoutPages(); |
|
viewport()->setUpdatesEnabled( true ); |
|
d->document->setViewportPage( page ); |
|
updateZoomText(); |
|
setFocus(); |
|
} |
|
|
|
void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNumber ) |
|
{ |
|
if ( !annotation ) |
|
return; |
|
|
|
// find the annot window |
|
AnnotWindow* existWindow = 0; |
|
QHash< Okular::Annotation *, AnnotWindow * >::ConstIterator it = d->m_annowindows.constFind( annotation ); |
|
if ( it != d->m_annowindows.constEnd() ) |
|
{ |
|
existWindow = *it; |
|
} |
|
|
|
if ( existWindow == 0 ) |
|
{ |
|
existWindow = new AnnotWindow( this, annotation, d->document, pageNumber ); |
|
connect(existWindow, SIGNAL(destroyed(QObject*)), this, SLOT(slotAnnotationWindowDestroyed(QObject*))); |
|
|
|
d->m_annowindows.insert( annotation, existWindow ); |
|
} |
|
|
|
existWindow->show(); |
|
} |
|
|
|
void PageView::slotAnnotationWindowDestroyed( QObject * window ) |
|
{ |
|
QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin(); |
|
QHash< Okular::Annotation*, AnnotWindow * >::Iterator itEnd = d->m_annowindows.end(); |
|
while ( it != itEnd ) |
|
{ |
|
if ( it.value() == window ) |
|
{ |
|
it = d->m_annowindows.erase( it ); |
|
} |
|
else |
|
{ |
|
++it; |
|
} |
|
} |
|
} |
|
|
|
void PageView::displayMessage( const QString & message, const QString & details, PageViewMessage::Icon icon, int duration ) |
|
{ |
|
if ( !Okular::Settings::showOSD() ) |
|
{ |
|
if (icon == PageViewMessage::Error) |
|
{ |
|
if ( !details.isEmpty() ) |
|
KMessageBox::detailedError( this, message, details ); |
|
else |
|
KMessageBox::error( this, message ); |
|
} |
|
return; |
|
} |
|
|
|
// hide messageWindow if string is empty |
|
if ( message.isEmpty() ) |
|
return d->messageWindow->hide(); |
|
|
|
// display message (duration is length dependant) |
|
if (duration==-1) |
|
{ |
|
duration = 500 + 100 * message.length(); |
|
if ( !details.isEmpty() ) |
|
duration += 500 + 100 * details.length(); |
|
} |
|
d->messageWindow->display( message, details, icon, duration ); |
|
} |
|
|
|
void PageView::reparseConfig() |
|
{ |
|
// set the scroll bars policies |
|
Qt::ScrollBarPolicy scrollBarMode = Okular::Settings::showScrollBars() ? |
|
Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff; |
|
if ( horizontalScrollBarPolicy() != scrollBarMode ) |
|
{ |
|
setHorizontalScrollBarPolicy( scrollBarMode ); |
|
setVerticalScrollBarPolicy( scrollBarMode ); |
|
} |
|
|
|
if ( Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Summary && |
|
( (int)Okular::Settings::viewColumns() != d->setting_viewCols ) ) |
|
{ |
|
d->setting_viewCols = Okular::Settings::viewColumns(); |
|
|
|
slotRelayoutPages(); |
|
} |
|
|
|
updatePageStep(); |
|
|
|
if ( d->annotator ) |
|
{ |
|
d->annotator->setEnabled( false ); |
|
d->annotator->reparseConfig(); |
|
if ( d->aToggleAnnotator->isChecked() ) |
|
slotToggleAnnotator( true ); |
|
} |
|
|
|
// Something like invert colors may have changed |
|
// As we don't have a way to find out the old value |
|
// We just update the viewport, this shouldn't be that bad |
|
// since it's just a repaint of pixmaps we already have |
|
viewport()->update(); |
|
} |
|
|
|
KActionCollection *PageView::actionCollection() const |
|
{ |
|
return d->actionCollection; |
|
} |
|
|
|
KAction *PageView::toggleFormsAction() const |
|
{ |
|
return d->aToggleForms; |
|
} |
|
|
|
int PageView::contentAreaWidth() const |
|
{ |
|
return horizontalScrollBar()->maximum() + viewport()->width(); |
|
} |
|
|
|
int PageView::contentAreaHeight() const |
|
{ |
|
return verticalScrollBar()->maximum() + viewport()->height(); |
|
} |
|
|
|
QPoint PageView::contentAreaPosition() const |
|
{ |
|
return QPoint( horizontalScrollBar()->value(), verticalScrollBar()->value() ); |
|
} |
|
|
|
QPoint PageView::contentAreaPoint( const QPoint & pos ) const |
|
{ |
|
return pos + contentAreaPosition(); |
|
} |
|
|
|
QPointF PageView::contentAreaPoint( const QPointF & pos ) const |
|
{ |
|
return pos + contentAreaPosition(); |
|
} |
|
|
|
QString PageViewPrivate::selectedText() const |
|
{ |
|
if ( pagesWithTextSelection.isEmpty() ) |
|
return QString(); |
|
|
|
QString text; |
|
QList< int > selpages = pagesWithTextSelection.toList(); |
|
qSort( selpages ); |
|
const Okular::Page * pg = 0; |
|
if ( selpages.count() == 1 ) |
|
{ |
|
pg = document->page( selpages.first() ); |
|
text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); |
|
} |
|
else |
|
{ |
|
pg = document->page( selpages.first() ); |
|
text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); |
|
int end = selpages.count() - 1; |
|
for( int i = 1; i < end; ++i ) |
|
{ |
|
pg = document->page( selpages.at( i ) ); |
|
text.append( pg->text( 0, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); |
|
} |
|
pg = document->page( selpages.last() ); |
|
text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); |
|
} |
|
return text; |
|
} |
|
|
|
void PageView::copyTextSelection() const |
|
{ |
|
const QString text = d->selectedText(); |
|
if ( !text.isEmpty() ) |
|
{ |
|
QClipboard *cb = QApplication::clipboard(); |
|
cb->setText( text, QClipboard::Clipboard ); |
|
} |
|
} |
|
|
|
void PageView::selectAll() |
|
{ |
|
QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd(); |
|
for ( ; it < itEnd; ++it ) |
|
{ |
|
Okular::RegularAreaRect * area = textSelectionForItem( *it ); |
|
d->pagesWithTextSelection.insert( (*it)->pageNumber() ); |
|
d->document->setPageTextSelection( (*it)->pageNumber(), area, palette().color( QPalette::Active, QPalette::Highlight ) ); |
|
} |
|
} |
|
|
|
//BEGIN DocumentObserver inherited methods |
|
void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags ) |
|
{ |
|
bool documentChanged = setupFlags & Okular::DocumentObserver::DocumentChanged; |
|
// reuse current pages if nothing new |
|
if ( ( pageSet.count() == d->items.count() ) && !documentChanged && !( setupFlags & Okular::DocumentObserver::NewLayoutForPages ) ) |
|
{ |
|
int count = pageSet.count(); |
|
for ( int i = 0; (i < count) && !documentChanged; i++ ) |
|
if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() ) |
|
documentChanged = true; |
|
if ( !documentChanged ) |
|
return; |
|
} |
|
|
|
// delete all widgets (one for each page in pageSet) |
|
QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); |
|
for ( ; dIt != dEnd; ++dIt ) |
|
delete *dIt; |
|
d->items.clear(); |
|
d->visibleItems.clear(); |
|
d->pagesWithTextSelection.clear(); |
|
toggleFormWidgets( false ); |
|
if ( d->formsWidgetController ) |
|
d->formsWidgetController->dropRadioButtons(); |
|
|
|
bool haspages = !pageSet.isEmpty(); |
|
bool hasformwidgets = false; |
|
// create children widgets |
|
QVector< Okular::Page * >::const_iterator setIt = pageSet.constBegin(), setEnd = pageSet.constEnd(); |
|
for ( ; setIt != setEnd; ++setIt ) |
|
{ |
|
PageViewItem * item = new PageViewItem( *setIt ); |
|
d->items.push_back( item ); |
|
#ifdef PAGEVIEW_DEBUG |
|
kDebug().nospace() << "cropped geom for " << d->items.last()->pageNumber() << " is " << d->items.last()->croppedGeometry(); |
|
#endif |
|
const QLinkedList< Okular::FormField * > pageFields = (*setIt)->formFields(); |
|
QLinkedList< Okular::FormField * >::const_iterator ffIt = pageFields.constBegin(), ffEnd = pageFields.constEnd(); |
|
for ( ; ffIt != ffEnd; ++ffIt ) |
|
{ |
|
Okular::FormField * ff = *ffIt; |
|
FormWidgetIface * w = FormWidgetFactory::createWidget( ff, viewport() ); |
|
if ( w ) |
|
{ |
|
w->setPageItem( item ); |
|
w->setFormWidgetsController( d->formWidgetsController() ); |
|
w->setVisibility( false ); |
|
w->setCanBeFilled( d->document->isAllowed( Okular::AllowFillForms ) ); |
|
item->formWidgets().insert( ff->id(), w ); |
|
hasformwidgets = true; |
|
} |
|
} |
|
const QLinkedList< Okular::Annotation * > annotations = (*setIt)->annotations(); |
|
QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.constBegin(), aEnd = annotations.constEnd(); |
|
for ( ; aIt != aEnd; ++aIt ) |
|
{ |
|
Okular::Annotation * a = *aIt; |
|
if ( a->subType() == Okular::Annotation::AMovie ) |
|
{ |
|
Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a ); |
|
VideoWidget * vw = new VideoWidget( movieAnn, movieAnn->movie(), d->document, viewport() ); |
|
item->videoWidgets().insert( movieAnn->movie(), vw ); |
|
vw->pageInitialized(); |
|
} |
|
else if ( a->subType() == Okular::Annotation::AScreen ) |
|
{ |
|
const Okular::ScreenAnnotation * screenAnn = static_cast< Okular::ScreenAnnotation * >( a ); |
|
Okular::Movie *movie = GuiUtils::renditionMovieFromScreenAnnotation( screenAnn ); |
|
if ( movie ) |
|
{ |
|
VideoWidget * vw = new VideoWidget( screenAnn, movie, d->document, viewport() ); |
|
item->videoWidgets().insert( movie, vw ); |
|
vw->pageInitialized(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// invalidate layout so relayout/repaint will happen on next viewport change |
|
if ( haspages ) |
|
{ |
|
// We do a delayed call to slotRelayoutPages but also set the dirtyLayout |
|
// because we might end up in notifyViewportChanged while slotRelayoutPages |
|
// has not been done and we don't want that to happen |
|
d->dirtyLayout = true; |
|
QMetaObject::invokeMethod(this, "slotRelayoutPages", Qt::QueuedConnection); |
|
} |
|
else |
|
{ |
|
// update the mouse cursor when closing because we may have close through a link and |
|
// want the cursor to come back to the normal cursor |
|
updateCursor(); |
|
// then, make the message window and scrollbars disappear, and trigger a repaint |
|
d->messageWindow->hide(); |
|
resizeContentArea( QSize( 0,0 ) ); |
|
viewport()->update(); // when there is no change to the scrollbars, no repaint would |
|
// be done and the old document would still be shown |
|
} |
|
|
|
// OSD to display pages |
|
if ( documentChanged && pageSet.count() > 0 && Okular::Settings::showOSD() ) |
|
d->messageWindow->display( |
|
i18np(" Loaded a one-page document.", |
|
" Loaded a %1-page document.", |
|
pageSet.count() ), |
|
QString(), |
|
PageViewMessage::Info, 4000 ); |
|
|
|
updateActionState( haspages, documentChanged, hasformwidgets ); |
|
|
|
// We need to assign it to a different list otherwise slotAnnotationWindowDestroyed |
|
// will bite us and clear d->m_annowindows |
|
QHash< Okular::Annotation *, AnnotWindow * > annowindows = d->m_annowindows; |
|
d->m_annowindows.clear(); |
|
qDeleteAll( annowindows ); |
|
|
|
selectionClear(); |
|
} |
|
|
|
void PageView::updateActionState( bool haspages, bool documentChanged, bool hasformwidgets ) |
|
{ |
|
if ( d->aPageSizes ) |
|
{ // may be null if dummy mode is on |
|
bool pageSizes = d->document->supportsPageSizes(); |
|
d->aPageSizes->setEnabled( pageSizes ); |
|
// set the new page sizes: |
|
// - if the generator supports them |
|
// - if the document changed |
|
if ( pageSizes && documentChanged ) |
|
{ |
|
QStringList items; |
|
foreach ( const Okular::PageSize &p, d->document->pageSizes() ) |
|
items.append( p.name() ); |
|
d->aPageSizes->setItems( items ); |
|
} |
|
} |
|
|
|
if ( d->aTrimMargins ) |
|
d->aTrimMargins->setEnabled( haspages ); |
|
|
|
if ( d->aViewMode ) |
|
d->aViewMode->setEnabled( haspages ); |
|
|
|
if ( d->aViewContinuous ) |
|
d->aViewContinuous->setEnabled( haspages ); |
|
|
|
if ( d->aZoomFitWidth ) |
|
d->aZoomFitWidth->setEnabled( haspages ); |
|
if ( d->aZoomFitPage ) |
|
d->aZoomFitPage->setEnabled( haspages ); |
|
if ( d->aZoomAutoFit ) |
|
d->aZoomAutoFit->setEnabled( haspages ); |
|
|
|
if ( d->aZoom ) |
|
{ |
|
d->aZoom->selectableActionGroup()->setEnabled( haspages ); |
|
d->aZoom->setEnabled( haspages ); |
|
} |
|
if ( d->aZoomIn ) |
|
d->aZoomIn->setEnabled( haspages ); |
|
if ( d->aZoomOut ) |
|
d->aZoomOut->setEnabled( haspages ); |
|
|
|
if ( d->mouseModeActionGroup ) |
|
d->mouseModeActionGroup->setEnabled( haspages ); |
|
|
|
if ( d->aRotateClockwise ) |
|
d->aRotateClockwise->setEnabled( haspages ); |
|
if ( d->aRotateCounterClockwise ) |
|
d->aRotateCounterClockwise->setEnabled( haspages ); |
|
if ( d->aRotateOriginal ) |
|
d->aRotateOriginal->setEnabled( haspages ); |
|
if ( d->aToggleForms ) |
|
{ // may be null if dummy mode is on |
|
d->aToggleForms->setEnabled( haspages && hasformwidgets ); |
|
} |
|
bool allowAnnotations = d->document->isAllowed( Okular::AllowNotes ); |
|
if ( d->annotator ) |
|
{ |
|
bool allowTools = haspages && allowAnnotations; |
|
d->annotator->setToolsEnabled( allowTools ); |
|
d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() ); |
|
} |
|
if ( d->aToggleAnnotator ) |
|
{ |
|
if ( !allowAnnotations && d->aToggleAnnotator->isChecked() ) |
|
{ |
|
d->aToggleAnnotator->trigger(); |
|
} |
|
d->aToggleAnnotator->setEnabled( allowAnnotations ); |
|
} |
|
if ( d->aSpeakDoc ) |
|
{ |
|
const bool enablettsactions = haspages ? Okular::Settings::useKTTSD() : false; |
|
d->aSpeakDoc->setEnabled( enablettsactions ); |
|
d->aSpeakPage->setEnabled( enablettsactions ); |
|
} |
|
if (d->aMouseMagnifier) |
|
d->aMouseMagnifier->setEnabled(d->document->supportsTiles()); |
|
} |
|
|
|
bool PageView::areSourceLocationsShownGraphically() const |
|
{ |
|
return Okular::Settings::showSourceLocationsGraphically(); |
|
} |
|
|
|
void PageView::setShowSourceLocationsGraphically(bool show) |
|
{ |
|
if( show == Okular::Settings::showSourceLocationsGraphically() ) |
|
{ |
|
return; |
|
} |
|
Okular::Settings::setShowSourceLocationsGraphically( show ); |
|
viewport()->update(); |
|
} |
|
|
|
void PageView::setLastSourceLocationViewport( const Okular::DocumentViewport& vp ) |
|
{ |
|
if( vp.rePos.enabled ) |
|
{ |
|
d->lastSourceLocationViewportNormalizedX = normClamp( vp.rePos.normalizedX, 0.5 ); |
|
d->lastSourceLocationViewportNormalizedY = normClamp( vp.rePos.normalizedY, 0.0 ); |
|
} |
|
else |
|
{ |
|
d->lastSourceLocationViewportNormalizedX = 0.5; |
|
d->lastSourceLocationViewportNormalizedY = 0.0; |
|
} |
|
d->lastSourceLocationViewportPageNumber = vp.pageNumber; |
|
viewport()->update(); |
|
} |
|
|
|
void PageView::clearLastSourceLocationViewport() |
|
{ |
|
d->lastSourceLocationViewportPageNumber = -1; |
|
d->lastSourceLocationViewportNormalizedX = 0.0; |
|
d->lastSourceLocationViewportNormalizedY = 0.0; |
|
viewport()->update(); |
|
} |
|
|
|
void PageView::notifyViewportChanged( bool smoothMove ) |
|
{ |
|
QMetaObject::invokeMethod(this, "slotRealNotifyViewportChanged", Qt::QueuedConnection, Q_ARG( bool, smoothMove )); |
|
} |
|
|
|
void PageView::slotRealNotifyViewportChanged( bool smoothMove ) |
|
{ |
|
// if we are the one changing viewport, skip this nofity |
|
if ( d->blockViewport ) |
|
return; |
|
|
|
// block setViewport outgoing calls |
|
d->blockViewport = true; |
|
|
|
// find PageViewItem matching the viewport description |
|
const Okular::DocumentViewport & vp = d->document->viewport(); |
|
PageViewItem * item = 0; |
|
QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); |
|
for ( ; iIt != iEnd; ++iIt ) |
|
if ( (*iIt)->pageNumber() == vp.pageNumber ) |
|
{ |
|
item = *iIt; |
|
break; |
|
} |
|
if ( !item ) |
|
{ |
|
kWarning() << "viewport for page" << vp.pageNumber << "has no matching item!"; |
|
d->blockViewport = false; |
|
return; |
|
} |
|
#ifdef PAGEVIEW_DEBUG |
|
kDebug() << "document viewport changed"; |
|
#endif |
|
// relayout in "Single Pages" mode or if a relayout is pending |
|
d->blockPixmapsRequest = true; |
|
if ( !Okular::Settings::viewContinuous() || d->dirtyLayout ) |
|
slotRelayoutPages(); |
|
|
|
// restore viewport center or use default {x-center,v-top} alignment |
|
const QRect & r = item->croppedGeometry(); |
|
int newCenterX = r.left(), |
|
newCenterY = r.top(); |
|
if ( vp.rePos.enabled ) |
|
{ |
|
if ( vp.rePos.pos == Okular::DocumentViewport::Center ) |
|
{ |
|
newCenterX += (int)( normClamp( vp.rePos.normalizedX, 0.5 ) * (double)r.width() ); |
|
newCenterY += (int)( normClamp( vp.rePos.normalizedY, 0.0 ) * (double)r.height() ); |
|
} |
|
else |
|
{ |
|
// TopLeft |
|
newCenterX += (int)( normClamp( vp.rePos.normalizedX, 0.0 ) * (double)r.width() + viewport()->width() / 2 ); |
|
newCenterY += (int)( normClamp( vp.rePos.normalizedY, 0.0 ) * (double)r.height() + viewport()->height() / 2 ); |
|
} |
|
} |
|
else |
|
{ |
|
newCenterX += r.width() / 2; |
|
newCenterY += viewport()->height() / 2 - 10; |
|
} |
|
|
|
// if smooth movement requested, setup parameters and start it |
|
if ( smoothMove ) |
|
{ |
|
d->viewportMoveActive = true; |
|
d->viewportMoveTime.start(); |
|
d->viewportMoveDest.setX( newCenterX ); |
|
d->viewportMoveDest.setY( newCenterY ); |
|
if ( !d->viewportMoveTimer ) |
|
{ |
|
d->viewportMoveTimer = new QTimer( this ); |
|
connect( d->viewportMoveTimer, SIGNAL(timeout()), |
|
this, SLOT(slotMoveViewport()) ); |
|
} |
|
d->viewportMoveTimer->start( 25 ); |
|
verticalScrollBar()->setEnabled( false ); |
|
horizontalScrollBar()->setEnabled( false ); |
|
} |
|
else |
|
center( newCenterX, newCenterY ); |
|
d->blockPixmapsRequest = false; |
|
|
|
// request visible pixmaps in the current viewport and recompute it |
|
slotRequestVisiblePixmaps(); |
|
|
|
// enable setViewport calls |
|
d->blockViewport = false; |
|
|
|
// update zoom text if in a ZoomFit/* zoom mode |
|
if ( d->zoomMode != ZoomFixed ) |
|
updateZoomText(); |
|
|
|
if( viewport() ) |
|
{ |
|
viewport()->update(); |
|
} |
|
|
|
// since the page has moved below cursor, update it |
|
updateCursor(); |
|
} |
|
|
|
void PageView::notifyPageChanged( int pageNumber, int changedFlags ) |
|
{ |
|
// only handle pixmap / highlight changes notifies |
|
if ( changedFlags & DocumentObserver::Bookmark ) |
|
return; |
|
|
|
if ( changedFlags & DocumentObserver::Annotations ) |
|
{ |
|
const QLinkedList< Okular::Annotation * > annots = d->document->page( pageNumber )->annotations(); |
|
const QLinkedList< Okular::Annotation * >::ConstIterator annItEnd = annots.end(); |
|
QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin(); |
|
for ( ; it != d->m_annowindows.end(); ) |
|
{ |
|
QLinkedList< Okular::Annotation * >::ConstIterator annIt = qFind( annots, it.key() ); |
|
if ( annIt != annItEnd ) |
|
{ |
|
(*it)->reloadInfo(); |
|
++it; |
|
} |
|
else |
|
{ |
|
AnnotWindow *w = *it; |
|
it = d->m_annowindows.erase( it ); |
|
// Need to delete after removing from the list |
|
// otherwise deleting will call slotAnnotationWindowDestroyed which will mess |
|
// the list and the iterators |
|
delete w; |
|
} |
|
} |
|
} |
|
|
|
if ( changedFlags & DocumentObserver::BoundingBox ) |
|
{ |
|
#ifdef PAGEVIEW_DEBUG |
|
kDebug() << "BoundingBox change on page" << pageNumber; |
|
#endif |
|
slotRelayoutPages(); |
|
slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! |
|
// Repaint the whole widget since layout may have changed |
|
viewport()->update(); |
|
return; |
|
} |
|
|
|
// iterate over visible items: if page(pageNumber) is one of them, repaint it |
|
QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); |
|
for ( ; iIt != iEnd; ++iIt ) |
|
if ( (*iIt)->pageNumber() == pageNumber && (*iIt)->isVisible() ) |
|
{ |
|
// update item's rectangle plus the little outline |
|
QRect expandedRect = (*iIt)->croppedGeometry(); |
|
// a PageViewItem is placed in the global page layout, |
|
// while we need to map its position in the viewport coordinates |
|
// (to get the correct area to repaint) |
|
expandedRect.translate( -contentAreaPosition() ); |
|
expandedRect.adjust( -1, -1, 3, 3 ); |
|
viewport()->update( expandedRect ); |
|
|
|
// if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor |
|
if ( cursor().shape() != Qt::SizeVerCursor ) |
|
{ |
|
// since the page has been regenerated below cursor, update it |
|
updateCursor(); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
void PageView::notifyContentsCleared( int changedFlags ) |
|
{ |
|
// if pixmaps were cleared, re-ask them |
|
if ( changedFlags & DocumentObserver::Pixmap ) |
|
QMetaObject::invokeMethod(this, "slotRequestVisiblePixmaps", Qt::QueuedConnection); |
|
} |
|
|
|
void PageView::notifyZoom( int factor ) |
|
{ |
|
if ( factor > 0 ) |
|
updateZoom( ZoomIn ); |
|
else |
|
updateZoom( ZoomOut ); |
|
} |
|
|
|
bool PageView::canUnloadPixmap( int pageNumber ) const |
|
{ |
|
if ( Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || |
|
Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal ) |
|
{ |
|
// if the item is visible, forbid unloading |
|
QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd(); |
|
for ( ; vIt != vEnd; ++vIt ) |
|
if ( (*vIt)->pageNumber() == pageNumber ) |
|
return false; |
|
} |
|
else |
|
{ |
|
// forbid unloading of the visible items, and of the previous and next |
|
QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd(); |
|
for ( ; vIt != vEnd; ++vIt ) |
|
if ( abs( (*vIt)->pageNumber() - pageNumber ) <= 1 ) |
|
return false; |
|
} |
|
// if hidden premit unloading |
|
return true; |
|
} |
|
|
|
void PageView::notifyCurrentPageChanged( int previous, int current ) |
|
{ |
|
if ( previous != -1 ) |
|
{ |
|
PageViewItem * item = d->items.at( previous ); |
|
if ( item ) |
|
{ |
|
Q_FOREACH ( VideoWidget *videoWidget, item->videoWidgets() ) |
|
videoWidget->pageLeft(); |
|
} |
|
} |
|
|
|
if ( current != -1 ) |
|
{ |
|
PageViewItem * item = d->items.at( current ); |
|
if ( item ) |
|
{ |
|
Q_FOREACH ( VideoWidget *videoWidget, item->videoWidgets() ) |
|
videoWidget->pageEntered(); |
|
} |
|
} |
|
} |
|
|
|
//END DocumentObserver inherited methods |
|
|
|
//BEGIN View inherited methods |
|
bool PageView::supportsCapability( ViewCapability capability ) const |
|
{ |
|
switch ( capability ) |
|
{ |
|
case Zoom: |
|
case ZoomModality: |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
Okular::View::CapabilityFlags PageView::capabilityFlags( ViewCapability capability ) const |
|
{ |
|
switch ( capability ) |
|
{ |
|
case Zoom: |
|
case ZoomModality: |
|
return CapabilityRead | CapabilityWrite | CapabilitySerializable; |
|
} |
|
return 0; |
|
} |
|
|
|
QVariant PageView::capability( ViewCapability capability ) const |
|
{ |
|
switch ( capability ) |
|
{ |
|
case Zoom: |
|
return d->zoomFactor; |
|
case ZoomModality: |
|
return d->zoomMode; |
|
} |
|
return QVariant(); |
|
} |
|
|
|
void PageView::setCapability( ViewCapability capability, const QVariant &option ) |
|
{ |
|
switch ( capability ) |
|
{ |
|
case Zoom: |
|
{ |
|
bool ok = true; |
|
double factor = option.toDouble( &ok ); |
|
if ( ok && factor > 0.0 ) |
|
{ |
|
d->zoomFactor = static_cast< float >( factor ); |
|
updateZoom( ZoomRefreshCurrent ); |
|
} |
|
break; |
|
} |
|
case ZoomModality: |
|
{ |
|
bool ok = true; |
|
int mode = option.toInt( &ok ); |
|
if ( ok ) |
|
{ |
|
if ( mode >= 0 && mode < 3 ) |
|
updateZoom( (ZoomMode)mode ); |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//END View inherited methods |
|
|
|
//BEGIN widget events |
|
void PageView::paintEvent(QPaintEvent *pe) |
|
{ |
|
const QPoint areaPos = contentAreaPosition(); |
|
// create the rect into contents from the clipped screen rect |
|
QRect viewportRect = viewport()->rect(); |
|
viewportRect.translate( areaPos ); |
|
QRect contentsRect = pe->rect().translated( areaPos ).intersect( viewportRect ); |
|
if ( !contentsRect.isValid() ) |
|
return; |
|
|
|
#ifdef PAGEVIEW_DEBUG |
|
kDebug() << "paintevent" << contentsRect; |
|
#endif |
|
|
|
// create the screen painter. a pixel painted at contentsX,contentsY |
|
// appears to the top-left corner of the scrollview. |
|
QPainter screenPainter( viewport() ); |
|
// translate to simulate the scrolled content widget |
|
screenPainter.translate( -areaPos ); |
|
|
|
// selectionRect is the normalized mouse selection rect |
|
QRect selectionRect = d->mouseSelectionRect; |
|
if ( !selectionRect.isNull() ) |
|
selectionRect = selectionRect.normalized(); |
|
// selectionRectInternal without the border |
|
QRect selectionRectInternal = selectionRect; |
|
selectionRectInternal.adjust( 1, 1, -1, -1 ); |
|
// color for blending |
|
QColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ? |
|
d->mouseSelectionColor : Qt::red; |
|
|
|
// subdivide region into rects |
|
const QVector<QRect> &allRects = pe->region().rects(); |
|
uint numRects = allRects.count(); |
|
|
|
// preprocess rects area to see if it worths or not using subdivision |
|
uint summedArea = 0; |
|
for ( uint i = 0; i < numRects; i++ ) |
|
{ |
|
const QRect & r = allRects[i]; |
|
summedArea += r.width() * r.height(); |
|
} |
|
// very elementary check: SUMj(Region[j].area) is less than boundingRect.area |
|
bool useSubdivision = summedArea < (0.6 * contentsRect.width() * contentsRect.height()); |
|
if ( !useSubdivision ) |
|
numRects = 1; |
|
|
|
// iterate over the rects (only one loop if not using subdivision) |
|
for ( uint i = 0; i < numRects; i++ ) |
|
{ |
|
if ( useSubdivision ) |
|
{ |
|
// set 'contentsRect' to a part of the sub-divided region |
|
contentsRect = allRects[i].translated( areaPos ).intersect( viewportRect ); |
|
if ( !contentsRect.isValid() ) |
|
continue; |
|
} |
|
#ifdef PAGEVIEW_DEBUG |
|
kDebug() << contentsRect; |
|
#endif |
|
|
|
// note: this check will take care of all things requiring alpha blending (not only selection) |
|
bool wantCompositing = !selectionRect.isNull() && contentsRect.intersects( selectionRect ); |
|
// also alpha-blend when there is a table selection... |
|
wantCompositing |= !d->tableSelectionParts.isEmpty(); |
|
|
|
if ( wantCompositing && Okular::Settings::enableCompositing() ) |
|
{ |
|
// create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0}) |
|
QPixmap doubleBuffer( contentsRect.size() ); |
|
QPainter pixmapPainter( &doubleBuffer ); |
|
pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() ); |
|
|
|
// 1) Layer 0: paint items and clear bg on unpainted rects |
|
drawDocumentOnPainter( contentsRect, &pixmapPainter ); |
|
// 2a) Layer 1a: paint (blend) transparent selection (rectangle) |
|
if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && |
|
!selectionRectInternal.contains( contentsRect ) ) |
|
{ |
|
QRect blendRect = selectionRectInternal.intersect( contentsRect ); |
|
// skip rectangles covered by the selection's border |
|
if ( blendRect.isValid() ) |
|
{ |
|
// grab current pixmap into a new one to colorize contents |
|
QPixmap blendedPixmap( blendRect.width(), blendRect.height() ); |
|
QPainter p( &blendedPixmap ); |
|
p.drawPixmap( 0, 0, doubleBuffer, |
|
blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(), |
|
blendRect.width(), blendRect.height() ); |
|
|
|
QColor blCol = selBlendColor.dark( 140 ); |
|
blCol.setAlphaF( 0.2 ); |
|
p.fillRect( blendedPixmap.rect(), blCol ); |
|
p.end(); |
|
// copy the blended pixmap back to its place |
|
pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap ); |
|
} |
|
// draw border (red if the selection is too small) |
|
pixmapPainter.setPen( selBlendColor ); |
|
pixmapPainter.drawRect( selectionRect.adjusted( 0, 0, -1, -1 ) ); |
|
} |
|
// 2b) Layer 1b: paint (blend) transparent selection (table) |
|
foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { |
|
QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); |
|
selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); |
|
QRect selectionPartRectInternal = selectionPartRect; |
|
selectionPartRectInternal.adjust( 1, 1, -1, -1 ); |
|
if ( !selectionPartRect.isNull() && selectionPartRect.intersects( contentsRect ) && |
|
!selectionPartRectInternal.contains( contentsRect ) ) |
|
{ |
|
QRect blendRect = selectionPartRectInternal.intersect( contentsRect ); |
|
// skip rectangles covered by the selection's border |
|
if ( blendRect.isValid() ) |
|
{ |
|
// grab current pixmap into a new one to colorize contents |
|
QPixmap blendedPixmap( blendRect.width(), blendRect.height() ); |
|
QPainter p( &blendedPixmap ); |
|
p.drawPixmap( 0, 0, doubleBuffer, |
|
blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(), |
|
blendRect.width(), blendRect.height() ); |
|
|
|
QColor blCol = d->mouseSelectionColor.dark( 140 ); |
|
blCol.setAlphaF( 0.2 ); |
|
p.fillRect( blendedPixmap.rect(), blCol ); |
|
p.end(); |
|
// copy the blended pixmap back to its place |
|
pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap ); |
|
} |
|
// draw border (red if the selection is too small) |
|
pixmapPainter.setPen( d->mouseSelectionColor ); |
|
pixmapPainter.drawRect( selectionPartRect.adjusted( 0, 0, -1, -1 ) ); |
|
} |
|
} |
|
drawTableDividers( &pixmapPainter ); |
|
// 3) Layer 1: give annotator painting control |
|
if ( d->annotator && d->annotator->routePaints( contentsRect ) ) |
|
d->annotator->routePaint( &pixmapPainter, contentsRect ); |
|
// 4) Layer 2: overlays |
|
if ( Okular::Settings::debugDrawBoundaries() ) |
|
{ |
|
pixmapPainter.setPen( Qt::blue ); |
|
pixmapPainter.drawRect( contentsRect ); |
|
} |
|
|
|
// finish painting and draw contents |
|
pixmapPainter.end(); |
|
screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer ); |
|
} |
|
else |
|
{ |
|
// 1) Layer 0: paint items and clear bg on unpainted rects |
|
drawDocumentOnPainter( contentsRect, &screenPainter ); |
|
// 2a) Layer 1a: paint opaque selection (rectangle) |
|
if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && |
|
!selectionRectInternal.contains( contentsRect ) ) |
|
{ |
|
screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) ); |
|
screenPainter.drawRect( selectionRect ); |
|
} |
|
// 2b) Layer 1b: paint opaque selection (table) |
|
foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { |
|
QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); |
|
selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); |
|
QRect selectionPartRectInternal = selectionPartRect; |
|
selectionPartRectInternal.adjust( 1, 1, -1, -1 ); |
|
if ( !selectionPartRect.isNull() && selectionPartRect.intersects( contentsRect ) && |
|
!selectionPartRectInternal.contains( contentsRect ) ) |
|
{ |
|
screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) ); |
|
screenPainter.drawRect( selectionPartRect ); |
|
} |
|
} |
|
drawTableDividers( &screenPainter ); |
|
// 3) Layer 1: give annotator painting control |
|
if ( d->annotator && d->annotator->routePaints( contentsRect ) ) |
|
d->annotator->routePaint( &screenPainter, contentsRect ); |
|
// 4) Layer 2: overlays |
|
if ( Okular::Settings::debugDrawBoundaries() ) |
|
{ |
|
screenPainter.setPen( Qt::red ); |
|
screenPainter.drawRect( contentsRect ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void PageView::drawTableDividers(QPainter * screenPainter) |
|
{ |
|
if (!d->tableSelectionParts.isEmpty()) { |
|
screenPainter->setPen( d->mouseSelectionColor.dark() ); |
|
if (d->tableDividersGuessed) { |
|
QPen p = screenPainter->pen(); |
|
p.setStyle( Qt::DashLine ); |
|
screenPainter->setPen( p ); |
|
} |
|
foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { |
|
QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); |
|
selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); |
|
QRect selectionPartRectInternal = selectionPartRect; |
|
selectionPartRectInternal.adjust( 1, 1, -1, -1 ); |
|
foreach(double col, d->tableSelectionCols) { |
|
if (col >= tsp.rectInSelection.left && col <= tsp.rectInSelection.right) { |
|
col = (col - tsp.rectInSelection.left) / (tsp.rectInSelection.right - tsp.rectInSelection.left); |
|
const int x = selectionPartRect.left() + col * selectionPartRect.width() + 0.5; |
|
screenPainter->drawLine( |
|
x, selectionPartRectInternal.top(), |
|
x, selectionPartRectInternal.top() + selectionPartRectInternal.height() |
|
); |
|
} |
|
} |
|
foreach(double row, d->tableSelectionRows) { |
|
if (row >= tsp.rectInSelection.top && row <= tsp.rectInSelection.bottom) { |
|
row = (row - tsp.rectInSelection.top) / (tsp.rectInSelection.bottom - tsp.rectInSelection.top); |
|
const int y = selectionPartRect.top() + row * selectionPartRect.height() + 0.5; |
|
screenPainter->drawLine( |
|
selectionPartRectInternal.left(), y, |
|
selectionPartRectInternal.left() + selectionPartRectInternal.width(), y |
|
); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
void PageView::resizeEvent( QResizeEvent *e ) |
|
{ |
|
if ( d->items.isEmpty() ) |
|
{ |
|
resizeContentArea( e->size() ); |
|
return; |
|
} |
|
|
|
if ( ( d->zoomMode == ZoomFitWidth || d->zoomMode == ZoomFitAuto ) && !verticalScrollBar()->isVisible() && qAbs(e->oldSize().height() - e->size().height()) < verticalScrollBar()->width() && d->verticalScrollBarVisible ) |
|
{ |
|
// this saves us from infinite resizing loop because of scrollbars appearing and disappearing |
|
// see bug 160628 for more info |
|
// TODO looks are still a bit ugly because things are left uncentered |
|
// but better a bit ugly than unusable |
|
d->verticalScrollBarVisible = false; |
|
resizeContentArea( e->size() ); |
|
return; |
|
} |
|
else if ( d->zoomMode == ZoomFitAuto && !horizontalScrollBar()->isVisible() && qAbs(e->oldSize().width() - e->size().width()) < horizontalScrollBar()->height() && d->horizontalScrollBarVisible ) |
|
{ |
|
// this saves us from infinite resizing loop because of scrollbars appearing and disappearing |
|
// TODO looks are still a bit ugly because things are left uncentered |
|
// but better a bit ugly than unusable |
|
d->horizontalScrollBarVisible = false; |
|
resizeContentArea( e->size() ); |
|
return; |
|
} |
|
|
|
// start a timer that will refresh the pixmap after 0.2s |
|
d->delayResizeEventTimer->start( 200 ); |
|
d->verticalScrollBarVisible = verticalScrollBar()->isVisible(); |
|
d->horizontalScrollBarVisible = horizontalScrollBar()->isVisible(); |
|
} |
|
|
|
void PageView::keyPressEvent( QKeyEvent * e ) |
|
{ |
|
e->accept(); |
|
|
|
// if performing a selection or dyn zooming, disable keys handling |
|
if ( ( d->mouseSelecting && e->key() != Qt::Key_Escape ) || ( QApplication::mouseButtons () & Qt::MidButton ) ) |
|
return; |
|
|
|
// if viewport is moving, disable keys handling |
|
if ( d->viewportMoveActive ) |
|
return; |
|
|
|
// move/scroll page by using keys |
|
switch ( e->key() ) |
|
{ |
|
case Qt::Key_J: |
|
case Qt::Key_K: |
|
case Qt::Key_Down: |
|
case Qt::Key_PageDown: |
|
case Qt::Key_Up: |
|
case Qt::Key_PageUp: |
|
case Qt::Key_Backspace: |
|
if ( e->key() == Qt::Key_Down |
|
|| e->key() == Qt::Key_PageDown |
|
|| e->key() == Qt::Key_J ) |
|
{ |
|
bool singleStep = e->key() == Qt::Key_Down || e->key() == Qt::Key_J; |
|
slotScrollDown( singleStep ); |
|
} |
|
else |
|
{ |
|
bool singleStep = e->key() == Qt::Key_Up || e->key() == Qt::Key_K; |
|
slotScrollUp( singleStep ); |
|
} |
|
break; |
|
case Qt::Key_Left: |
|
case Qt::Key_H: |
|
if ( horizontalScrollBar()->maximum() == 0 ) |
|
{ |
|
//if we cannot scroll we go to the previous page vertically |
|
int next_page = d->document->currentPage() - viewColumns(); |
|
d->document->setViewportPage(next_page); |
|
} |
|
else |
|
horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub ); |
|
break; |
|
case Qt::Key_Right: |
|
case Qt::Key_L: |
|
if ( horizontalScrollBar()->maximum() == 0 ) |
|
{ |
|
//if we cannot scroll we advance the page vertically |
|
int next_page = d->document->currentPage() + viewColumns(); |
|
d->document->setViewportPage(next_page); |
|
} |
|
else |
|
horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd ); |
|
break; |
|
case Qt::Key_Escape: |
|
emit escPressed(); |
|
selectionClear( d->tableDividersGuessed ? ClearOnlyDividers : ClearAllSelection ); |
|
d->mousePressPos = QPoint(); |
|
if ( d->aPrevAction ) |
|
{ |
|
d->aPrevAction->trigger(); |
|
d->aPrevAction = 0; |
|
} |
|
break; |
|
case Qt::Key_Shift: |
|
case Qt::Key_Control: |
|
if ( d->autoScrollTimer ) |
|
{ |
|
if ( d->autoScrollTimer->isActive() ) |
|
d->autoScrollTimer->stop(); |
|
else |
|
slotAutoScoll(); |
|
return; |
|
} |
|
// else fall trhough |
|
default: |
|
e->ignore(); |
|
return; |
|
} |
|
// if a known key has been pressed, stop scrolling the page |
|
if ( d->autoScrollTimer ) |
|
{ |
|
d->scrollIncrement = 0; |
|
d->autoScrollTimer->stop(); |
|
} |
|
} |
|
|
|
void PageView::keyReleaseEvent( QKeyEvent * e ) |
|
{ |
|
e->accept(); |
|
|
|
if ( d->annotator && d->annotator->active() ) |
|
{ |
|
if ( d->annotator->routeKeyEvent( e ) ) |
|
return; |
|
} |
|
|
|
if ( e->key() == Qt::Key_Escape && d->autoScrollTimer ) |
|
{ |
|
d->scrollIncrement = 0; |
|
d->autoScrollTimer->stop(); |
|
} |
|
} |
|
|
|
void PageView::inputMethodEvent( QInputMethodEvent * e ) |
|
{ |
|
Q_UNUSED(e) |
|
} |
|
|
|
static QPoint rotateInRect( const QPoint &rotated, Okular::Rotation rotation ) |
|
{ |
|
QPoint ret; |
|
|
|
switch ( rotation ) |
|
{ |
|
case Okular::Rotation90: |
|
ret = QPoint( rotated.y(), -rotated.x() ); |
|
break; |
|
case Okular::Rotation180: |
|
ret = QPoint( -rotated.x(), -rotated.y() ); |
|
break; |
|
case Okular::Rotation270: |
|
ret = QPoint( -rotated.y(), rotated.x() ); |
|
break; |
|
case Okular::Rotation0: // no modifications |
|
default: // other cases |
|
ret = rotated; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
void PageView::tabletEvent( QTabletEvent * e ) |
|
{ |
|
// Ignore tablet events that we don't care about |
|
if ( !( e->type() == QEvent::TabletPress || |
|
e->type() == QEvent::TabletRelease || |
|
e->type() == QEvent::TabletMove ) ) |
|
{ |
|
e->ignore(); |
|
return; |
|
} |
|
|
|
// Determine pen state |
|
bool penReleased = false; |
|
if ( e->type() == QEvent::TabletPress ) |
|
{ |
|
d->penDown = true; |
|
} |
|
if ( e->type() == QEvent::TabletRelease ) |
|
{ |
|
d->penDown = false; |
|
penReleased = true; |
|
} |
|
|
|
// If we're editing an annotation and the tablet pen is either down or just released |
|
// then dispatch event to annotator |
|
if ( d->annotator && d->annotator->active() && ( d->penDown || penReleased ) ) |
|
{ |
|
const QPoint eventPos = contentAreaPoint( e->pos() ); |
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
const QPoint localOriginInGlobal = mapToGlobal( QPoint(0,0) ); |
|
|
|
// routeTabletEvent will accept or ignore event as appropriate |
|
d->annotator->routeTabletEvent( e, pageItem, localOriginInGlobal ); |
|
} else { |
|
e->ignore(); |
|
} |
|
} |
|
|
|
void PageView::mouseMoveEvent( QMouseEvent * e ) |
|
{ |
|
// don't perform any mouse action when no document is shown |
|
if ( d->items.isEmpty() ) |
|
return; |
|
|
|
// don't perform any mouse action when viewport is autoscrolling |
|
if ( d->viewportMoveActive ) |
|
return; |
|
|
|
// if holding mouse mid button, perform zoom |
|
if ( e->buttons() & Qt::MidButton ) |
|
{ |
|
int mouseY = e->globalPos().y(); |
|
int deltaY = d->mouseMidLastY - mouseY; |
|
|
|
// wrap mouse from top to bottom |
|
const QRect mouseContainer = KGlobalSettings::desktopGeometry( this ); |
|
const int absDeltaY = abs(deltaY); |
|
if ( absDeltaY > mouseContainer.height() / 2 ) |
|
{ |
|
deltaY = mouseContainer.height() - absDeltaY; |
|
} |
|
|
|
const float upperZoomLimit = d->document->supportsTiles() ? 15.99 : 3.99; |
|
if ( mouseY <= mouseContainer.top() + 4 && |
|
d->zoomFactor < upperZoomLimit ) |
|
{ |
|
mouseY = mouseContainer.bottom() - 5; |
|
QCursor::setPos( e->globalPos().x(), mouseY ); |
|
} |
|
// wrap mouse from bottom to top |
|
else if ( mouseY >= mouseContainer.bottom() - 4 && |
|
d->zoomFactor > 0.101 ) |
|
{ |
|
mouseY = mouseContainer.top() + 5; |
|
QCursor::setPos( e->globalPos().x(), mouseY ); |
|
} |
|
// remember last position |
|
d->mouseMidLastY = mouseY; |
|
|
|
// update zoom level, perform zoom and redraw |
|
if ( deltaY ) |
|
{ |
|
d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) ); |
|
updateZoom( ZoomRefreshCurrent ); |
|
viewport()->repaint(); |
|
} |
|
return; |
|
} |
|
|
|
const QPoint eventPos = contentAreaPoint( e->pos() ); |
|
|
|
// if we're editing an annotation, dispatch event to it |
|
if ( d->annotator && d->annotator->active() ) |
|
{ |
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
updateCursor( eventPos ); |
|
d->annotator->routeMouseEvent( e, pageItem ); |
|
return; |
|
} |
|
|
|
bool leftButton = (e->buttons() == Qt::LeftButton); |
|
bool rightButton = (e->buttons() == Qt::RightButton); |
|
switch ( Okular::Settings::mouseMode() ) |
|
{ |
|
case Okular::Settings::EnumMouseMode::Browse: |
|
if ( leftButton ) |
|
{ |
|
d->leftClickTimer.stop(); |
|
|
|
if ( d->mouseAnn ) |
|
{ |
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
if ( pageItem ) |
|
{ |
|
const QRect & itemRect = pageItem->uncroppedGeometry(); |
|
QPoint newpos = eventPos - itemRect.topLeft(); |
|
QPoint p( newpos - d->mouseAnnPos ); |
|
QPointF pf( rotateInRect( p, pageItem->page()->rotation() ) ); |
|
if ( pageItem->page()->rotation() % 2 == 0 ) |
|
{ |
|
pf.rx() /= pageItem->uncroppedWidth(); |
|
pf.ry() /= pageItem->uncroppedHeight(); |
|
} |
|
else |
|
{ |
|
pf.rx() /= pageItem->uncroppedHeight(); |
|
pf.ry() /= pageItem->uncroppedWidth(); |
|
} |
|
|
|
d->document->translatePageAnnotation(d->mouseAnnPageNum, d->mouseAnn, Okular::NormalizedPoint( pf.x(), pf.y() ) ); |
|
d->mouseAnnPos = newpos; |
|
} |
|
} |
|
// drag page |
|
else if ( !d->mouseGrabPos.isNull() ) |
|
{ |
|
setCursor( Qt::ClosedHandCursor ); |
|
|
|
QPoint mousePos = e->globalPos(); |
|
QPoint delta = d->mouseGrabPos - mousePos; |
|
|
|
// wrap mouse from top to bottom |
|
const QRect mouseContainer = KGlobalSettings::desktopGeometry( this ); |
|
// If the delta is huge it probably means we just wrapped in that direction |
|
const QPoint absDelta(abs(delta.x()), abs(delta.y())); |
|
if ( absDelta.y() > mouseContainer.height() / 2 ) |
|
{ |
|
delta.setY(mouseContainer.height() - absDelta.y()); |
|
} |
|
if ( absDelta.x() > mouseContainer.width() / 2 ) |
|
{ |
|
delta.setX(mouseContainer.width() - absDelta.x()); |
|
} |
|
if ( mousePos.y() <= mouseContainer.top() + 4 && |
|
verticalScrollBar()->value() < verticalScrollBar()->maximum() - 10 ) |
|
{ |
|
mousePos.setY( mouseContainer.bottom() - 5 ); |
|
QCursor::setPos( mousePos ); |
|
} |
|
// wrap mouse from bottom to top |
|
else if ( mousePos.y() >= mouseContainer.bottom() - 4 && |
|
verticalScrollBar()->value() > 10 ) |
|
{ |
|
mousePos.setY( mouseContainer.top() + 5 ); |
|
QCursor::setPos( mousePos ); |
|
} |
|
// remember last position |
|
d->mouseGrabPos = mousePos; |
|
|
|
// scroll page by position increment |
|
scrollTo( horizontalScrollBar()->value() + delta.x(), verticalScrollBar()->value() + delta.y() ); |
|
} |
|
} |
|
else if ( rightButton && !d->mousePressPos.isNull() && d->aMouseSelect ) |
|
{ |
|
// if mouse moves 5 px away from the press point, switch to 'selection' |
|
int deltaX = d->mousePressPos.x() - e->globalPos().x(), |
|
deltaY = d->mousePressPos.y() - e->globalPos().y(); |
|
if ( deltaX > 5 || deltaX < -5 || deltaY > 5 || deltaY < -5 ) |
|
{ |
|
d->aPrevAction = d->aMouseNormal; |
|
d->aMouseSelect->trigger(); |
|
QPoint newPos = eventPos + QPoint( deltaX, deltaY ); |
|
selectionStart( newPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); |
|
updateSelection( eventPos ); |
|
break; |
|
} |
|
} |
|
else |
|
{ |
|
// only hovering the page, so update the cursor |
|
updateCursor(); |
|
} |
|
break; |
|
|
|
case Okular::Settings::EnumMouseMode::Zoom: |
|
case Okular::Settings::EnumMouseMode::RectSelect: |
|
case Okular::Settings::EnumMouseMode::TableSelect: |
|
// set second corner of selection |
|
if ( d->mouseSelecting ) |
|
updateSelection( eventPos ); |
|
break; |
|
|
|
case Okular::Settings::EnumMouseMode::Magnifier: |
|
if ( e->buttons() ) // if any button is pressed at all |
|
{ |
|
moveMagnifier( e->pos() ); |
|
updateMagnifier( eventPos ); |
|
} |
|
break; |
|
|
|
case Okular::Settings::EnumMouseMode::TextSelect: |
|
// if mouse moves 5 px away from the press point and the document soupports text extraction, do 'textselection' |
|
if ( !d->mouseTextSelecting && !d->mousePressPos.isNull() && d->document->supportsSearching() && ( ( eventPos - d->mouseSelectPos ).manhattanLength() > 5 ) ) |
|
{ |
|
d->mouseTextSelecting = true; |
|
} |
|
updateSelection( eventPos ); |
|
updateCursor(); |
|
break; |
|
} |
|
} |
|
|
|
void PageView::mousePressEvent( QMouseEvent * e ) |
|
{ |
|
// don't perform any mouse action when no document is shown |
|
if ( d->items.isEmpty() ) |
|
return; |
|
|
|
// if performing a selection or dyn zooming, disable mouse press |
|
if ( d->mouseSelecting || ( e->button() != Qt::MidButton && ( e->buttons() & Qt::MidButton) ) || d->viewportMoveActive ) |
|
return; |
|
|
|
// if the page is scrolling, stop it |
|
if ( d->autoScrollTimer ) |
|
{ |
|
d->scrollIncrement = 0; |
|
d->autoScrollTimer->stop(); |
|
} |
|
|
|
// if pressing mid mouse button while not doing other things, begin 'continuous zoom' mode |
|
if ( e->button() == Qt::MidButton ) |
|
{ |
|
d->mouseMidLastY = e->globalPos().y(); |
|
setCursor( Qt::SizeVerCursor ); |
|
return; |
|
} |
|
|
|
const QPoint eventPos = contentAreaPoint( e->pos() ); |
|
|
|
// if we're editing an annotation, dispatch event to it |
|
if ( d->annotator && d->annotator->active() ) |
|
{ |
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
d->annotator->routeMouseEvent( e, pageItem ); |
|
return; |
|
} |
|
|
|
// trigger history navigation for additional mouse buttons |
|
if ( e->button() == Qt::XButton1 ) |
|
{ |
|
emit mouseBackButtonClick(); |
|
return; |
|
} |
|
if ( e->button() == Qt::XButton2 ) |
|
{ |
|
emit mouseForwardButtonClick(); |
|
return; |
|
} |
|
|
|
// update press / 'start drag' mouse position |
|
d->mousePressPos = e->globalPos(); |
|
|
|
// handle mode dependant mouse press actions |
|
bool leftButton = e->button() == Qt::LeftButton, |
|
rightButton = e->button() == Qt::RightButton; |
|
|
|
// Not sure we should erase the selection when clicking with left. |
|
if ( Okular::Settings::mouseMode() != Okular::Settings::EnumMouseMode::TextSelect ) |
|
textSelectionClear(); |
|
|
|
switch ( Okular::Settings::mouseMode() ) |
|
{ |
|
case Okular::Settings::EnumMouseMode::Browse: // drag start / click / link following |
|
if ( leftButton ) |
|
{ |
|
PageViewItem * pageItem = 0; |
|
if ( ( e->modifiers() & Qt::ControlModifier ) && ( pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ) ) ) |
|
{ |
|
// find out normalized mouse coords inside current item |
|
const QRect & itemRect = pageItem->uncroppedGeometry(); |
|
double nX = pageItem->absToPageX(eventPos.x()); |
|
double nY = pageItem->absToPageY(eventPos.y()); |
|
const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() ); |
|
d->mouseAnnPos = eventPos - itemRect.topLeft(); |
|
if ( orect ) |
|
d->mouseAnn = ( (Okular::AnnotationObjectRect *)orect )->annotation(); |
|
// consider no annotation caught if its type is not movable |
|
if ( d->mouseAnn && !d->mouseAnn->canBeMoved() ) |
|
d->mouseAnn = 0; |
|
} |
|
if ( d->mouseAnn ) |
|
{ |
|
d->mouseAnn->setFlags( d->mouseAnn->flags() | Okular::Annotation::BeingMoved ); |
|
d->mouseAnnPageNum = pageItem->pageNumber(); |
|
} |
|
else |
|
{ |
|
d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mousePressPos; |
|
if ( !d->mouseOnRect ) |
|
d->leftClickTimer.start( QApplication::doubleClickInterval() + 10 ); |
|
} |
|
} |
|
else if ( rightButton ) |
|
{ |
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
if ( pageItem ) |
|
{ |
|
// find out normalized mouse coords inside current item |
|
const QRect & itemRect = pageItem->uncroppedGeometry(); |
|
double nX = pageItem->absToPageX(eventPos.x()); |
|
double nY = pageItem->absToPageY(eventPos.y()); |
|
|
|
const QLinkedList< const Okular::ObjectRect *> orects = pageItem->page()->objectRects( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() ); |
|
|
|
if ( !orects.isEmpty() ) |
|
{ |
|
AnnotationPopup popup( d->document, AnnotationPopup::MultiAnnotationMode, this ); |
|
|
|
foreach ( const Okular::ObjectRect * orect, orects ) |
|
{ |
|
Okular::Annotation * ann = ( (Okular::AnnotationObjectRect *)orect )->annotation(); |
|
if ( ann && (ann->subType() != Okular::Annotation::AWidget) ) |
|
popup.addAnnotation( ann, pageItem->pageNumber() ); |
|
|
|
} |
|
|
|
connect( &popup, SIGNAL(openAnnotationWindow(Okular::Annotation*,int)), |
|
this, SLOT(openAnnotationWindow(Okular::Annotation*,int)) ); |
|
|
|
popup.exec( e->globalPos() ); |
|
} |
|
} |
|
} |
|
break; |
|
|
|
case Okular::Settings::EnumMouseMode::Zoom: // set first corner of the zoom rect |
|
if ( leftButton ) |
|
selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ), false ); |
|
else if ( rightButton ) |
|
updateZoom( ZoomOut ); |
|
break; |
|
|
|
case Okular::Settings::EnumMouseMode::Magnifier: |
|
moveMagnifier( e->pos() ); |
|
d->magnifierView->show(); |
|
updateMagnifier( eventPos ); |
|
break; |
|
|
|
case Okular::Settings::EnumMouseMode::RectSelect: // set first corner of the selection rect |
|
if ( leftButton ) |
|
{ |
|
selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); |
|
} |
|
break; |
|
case Okular::Settings::EnumMouseMode::TableSelect: |
|
if ( leftButton ) |
|
{ |
|
if (d->tableSelectionParts.isEmpty()) |
|
{ |
|
selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); |
|
} else { |
|
QRect updatedRect; |
|
foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { |
|
QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); |
|
selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); |
|
|
|
// This will update the whole table rather than just the added/removed divider |
|
// (which can span more than one part). |
|
updatedRect = updatedRect.united(selectionPartRect); |
|
|
|
if (!selectionPartRect.contains(eventPos)) |
|
continue; |
|
|
|
// At this point it's clear we're either adding or removing a divider manually, so obviously the user is happy with the guess (if any). |
|
d->tableDividersGuessed = false; |
|
|
|
// There's probably a neat trick to finding which edge it's closest to, |
|
// but this way has the advantage of simplicity. |
|
const int fromLeft = abs(selectionPartRect.left() - eventPos.x()); |
|
const int fromRight = abs(selectionPartRect.left() + selectionPartRect.width() - eventPos.x()); |
|
const int fromTop = abs(selectionPartRect.top() - eventPos.y()); |
|
const int fromBottom = abs(selectionPartRect.top() + selectionPartRect.height() - eventPos.y()); |
|
const int colScore = fromTop<fromBottom ? fromTop : fromBottom; |
|
const int rowScore = fromLeft<fromRight ? fromLeft : fromRight; |
|
|
|
if (colScore < rowScore) { |
|
bool deleted=false; |
|
for(int i=0; i<d->tableSelectionCols.length(); i++) { |
|
const double col = (d->tableSelectionCols[i] - tsp.rectInSelection.left) / (tsp.rectInSelection.right - tsp.rectInSelection.left); |
|
const int colX = selectionPartRect.left() + col * selectionPartRect.width() + 0.5; |
|
if (abs(colX - eventPos.x())<=3) { |
|
d->tableSelectionCols.removeAt(i); |
|
deleted=true; |
|
|
|
break; |
|
} |
|
} |
|
if (!deleted) { |
|
double col = eventPos.x() - selectionPartRect.left(); |
|
col /= selectionPartRect.width(); // at this point, it's normalised within the part |
|
col *= (tsp.rectInSelection.right - tsp.rectInSelection.left); |
|
col += tsp.rectInSelection.left; // at this point, it's normalised within the whole table |
|
|
|
d->tableSelectionCols.append(col); |
|
qSort(d->tableSelectionCols); |
|
} |
|
} else { |
|
bool deleted=false; |
|
for(int i=0; i<d->tableSelectionRows.length(); i++) { |
|
const double row = (d->tableSelectionRows[i] - tsp.rectInSelection.top) / (tsp.rectInSelection.bottom - tsp.rectInSelection.top); |
|
const int rowY = selectionPartRect.top() + row * selectionPartRect.height() + 0.5; |
|
if (abs(rowY - eventPos.y())<=3) { |
|
d->tableSelectionRows.removeAt(i); |
|
deleted=true; |
|
|
|
break; |
|
} |
|
} |
|
if (!deleted) { |
|
double row = eventPos.y() - selectionPartRect.top(); |
|
row /= selectionPartRect.height(); // at this point, it's normalised within the part |
|
row *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); |
|
row += tsp.rectInSelection.top; // at this point, it's normalised within the whole table |
|
|
|
d->tableSelectionRows.append(row); |
|
qSort(d->tableSelectionRows); |
|
} |
|
} |
|
} |
|
updatedRect.translate( -contentAreaPosition() ); |
|
viewport()->update( updatedRect ); |
|
} |
|
} |
|
break; |
|
case Okular::Settings::EnumMouseMode::TextSelect: |
|
d->mouseSelectPos = eventPos; |
|
if ( !rightButton ) |
|
{ |
|
textSelectionClear(); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
void PageView::mouseReleaseEvent( QMouseEvent * e ) |
|
{ |
|
// stop the drag scrolling |
|
d->dragScrollTimer.stop(); |
|
|
|
d->leftClickTimer.stop(); |
|
|
|
// don't perform any mouse action when no document is shown.. |
|
if ( d->items.isEmpty() ) |
|
{ |
|
// ..except for right Clicks (emitted even it viewport is empty) |
|
if ( e->button() == Qt::RightButton ) |
|
emit rightClick( 0, e->globalPos() ); |
|
return; |
|
} |
|
|
|
// don't perform any mouse action when viewport is autoscrolling |
|
if ( d->viewportMoveActive ) |
|
return; |
|
|
|
const QPoint eventPos = contentAreaPoint( e->pos() ); |
|
|
|
// handle mode indepent mid buttom zoom |
|
if ( e->button() == Qt::MidButton ) |
|
{ |
|
// request pixmaps since it was disabled during drag |
|
slotRequestVisiblePixmaps(); |
|
// the cursor may now be over a link.. update it |
|
updateCursor( eventPos ); |
|
return; |
|
} |
|
|
|
// if we're editing an annotation, dispatch event to it |
|
if ( d->annotator && d->annotator->active() ) |
|
{ |
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
d->annotator->routeMouseEvent( e, pageItem ); |
|
return; |
|
} |
|
|
|
if ( d->mouseAnn ) |
|
{ |
|
// Just finished to move the annotation |
|
d->mouseAnn->setFlags( d->mouseAnn->flags() & ~Okular::Annotation::BeingMoved ); |
|
d->document->translatePageAnnotation(d->mouseAnnPageNum, d->mouseAnn, Okular::NormalizedPoint( 0.0, 0.0 ) ); |
|
setCursor( Qt::ArrowCursor ); |
|
d->mouseAnn = 0; |
|
} |
|
|
|
bool leftButton = e->button() == Qt::LeftButton; |
|
bool rightButton = e->button() == Qt::RightButton; |
|
switch ( Okular::Settings::mouseMode() ) |
|
{ |
|
case Okular::Settings::EnumMouseMode::Browse:{ |
|
// return the cursor to its normal state after dragging |
|
if ( cursor().shape() == Qt::ClosedHandCursor ) |
|
updateCursor( eventPos ); |
|
|
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
const QPoint pressPos = contentAreaPoint( mapFromGlobal( d->mousePressPos ) ); |
|
const PageViewItem * pageItemPressPos = pickItemOnPoint( pressPos.x(), pressPos.y() ); |
|
|
|
// if the mouse has not moved since the press, that's a -click- |
|
if ( leftButton && pageItem && pageItem == pageItemPressPos && |
|
( (d->mousePressPos - e->globalPos()).manhattanLength() < QApplication::startDragDistance() ) ) |
|
{ |
|
double nX = pageItem->absToPageX(eventPos.x()); |
|
double nY = pageItem->absToPageY(eventPos.y()); |
|
const Okular::ObjectRect * rect; |
|
rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
if ( rect ) |
|
{ |
|
// handle click over a link |
|
const Okular::Action * action = static_cast< const Okular::Action * >( rect->object() ); |
|
d->document->processAction( action ); |
|
} |
|
else if ( e->modifiers() == Qt::ShiftModifier ) |
|
{ |
|
// TODO: find a better way to activate the source reference "links" |
|
// for the moment they are activated with Shift + left click |
|
// Search the nearest source reference. |
|
rect = pageItem->page()->objectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
if ( !rect ) |
|
{ |
|
static const double s_minDistance = 0.025; // FIXME?: empirical value? |
|
double distance = 0.0; |
|
rect = pageItem->page()->nearestObjectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight(), &distance ); |
|
if ( rect && ( distance > s_minDistance ) ) |
|
rect = 0; |
|
} |
|
if ( rect ) |
|
{ |
|
const Okular::SourceReference * ref = static_cast< const Okular::SourceReference * >( rect->object() ); |
|
d->document->processSourceReference( ref ); |
|
} |
|
else |
|
{ |
|
const Okular::SourceReference * ref = d->document->dynamicSourceReference( pageItem-> pageNumber(), nX * pageItem->page()->width(), nY * pageItem->page()->height() ); |
|
if ( ref ) |
|
{ |
|
d->document->processSourceReference( ref ); |
|
delete ref; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
Okular::Annotation *ann = 0; |
|
|
|
rect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
if ( rect ) |
|
ann = ( (Okular::AnnotationObjectRect *)rect )->annotation(); |
|
|
|
if ( ann ) |
|
{ |
|
if ( ann->subType() == Okular::Annotation::AMovie ) |
|
{ |
|
VideoWidget *vw = pageItem->videoWidgets().value( static_cast<Okular::MovieAnnotation*>( ann )->movie() ); |
|
vw->show(); |
|
vw->play(); |
|
} |
|
else if ( ann->subType() == Okular::Annotation::AScreen ) |
|
{ |
|
d->document->processAction( static_cast<Okular::ScreenAnnotation*>( ann )->action() ); |
|
} |
|
} |
|
#if 0 |
|
// a link can move us to another page or even to another document, there's no point in trying to |
|
// process the click on the image once we have processes the click on the link |
|
rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->width(), pageItem->height() ); |
|
if ( rect ) |
|
{ |
|
// handle click over a image |
|
} |
|
/* Enrico and me have decided this is not worth the trouble it generates |
|
else |
|
{ |
|
// if not on a rect, the click selects the page |
|
// if ( pageItem->pageNumber() != (int)d->document->currentPage() ) |
|
d->document->setViewportPage( pageItem->pageNumber(), this ); |
|
}*/ |
|
#endif |
|
} |
|
} |
|
else if ( rightButton ) |
|
{ |
|
if ( pageItem && pageItem == pageItemPressPos && |
|
( (d->mousePressPos - e->globalPos()).manhattanLength() < QApplication::startDragDistance() ) ) |
|
{ |
|
double nX = pageItem->absToPageX(eventPos.x()); |
|
double nY = pageItem->absToPageY(eventPos.y()); |
|
const Okular::ObjectRect * rect; |
|
rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
if ( rect ) |
|
{ |
|
// handle right click over a link |
|
const Okular::Action * link = static_cast< const Okular::Action * >( rect->object() ); |
|
// creating the menu and its actions |
|
KMenu menu( this ); |
|
QAction * actProcessLink = menu.addAction( i18n( "Follow This Link" ) ); |
|
QAction * actStopSound = 0; |
|
if ( link->actionType() == Okular::Action::Sound ) |
|
actStopSound = menu.addAction( i18n( "Stop Sound" ) ); |
|
QAction * actCopyLinkLocation = 0; |
|
if ( dynamic_cast< const Okular::BrowseAction * >( link ) ) |
|
actCopyLinkLocation = menu.addAction( KIcon( "edit-copy" ), i18n( "Copy Link Address" ) ); |
|
QAction * res = menu.exec( e->globalPos() ); |
|
if ( res ) |
|
{ |
|
if ( res == actProcessLink ) |
|
{ |
|
d->document->processAction( link ); |
|
} |
|
else if ( res == actCopyLinkLocation ) |
|
{ |
|
const Okular::BrowseAction * browseLink = static_cast< const Okular::BrowseAction * >( link ); |
|
QClipboard *cb = QApplication::clipboard(); |
|
cb->setText( browseLink->url(), QClipboard::Clipboard ); |
|
if ( cb->supportsSelection() ) |
|
cb->setText( browseLink->url(), QClipboard::Selection ); |
|
} |
|
else if ( res == actStopSound ) |
|
{ |
|
Okular::AudioPlayer::instance()->stopPlaybacks(); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// a link can move us to another page or even to another document, there's no point in trying to |
|
// process the click on the image once we have processes the click on the link |
|
rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
if ( rect ) |
|
{ |
|
// handle right click over a image |
|
} |
|
else |
|
{ |
|
// right click (if not within 5 px of the press point, the mode |
|
// had been already changed to 'Selection' instead of 'Normal') |
|
emit rightClick( pageItem->page(), e->globalPos() ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// right click (if not within 5 px of the press point, the mode |
|
// had been already changed to 'Selection' instead of 'Normal') |
|
emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() ); |
|
} |
|
} |
|
}break; |
|
|
|
case Okular::Settings::EnumMouseMode::Zoom: |
|
// if a selection rect has been defined, zoom into it |
|
if ( leftButton && d->mouseSelecting ) |
|
{ |
|
QRect selRect = d->mouseSelectionRect.normalized(); |
|
if ( selRect.width() <= 8 && selRect.height() <= 8 ) |
|
{ |
|
selectionClear(); |
|
break; |
|
} |
|
|
|
// find out new zoom ratio and normalized view center (relative to the contentsRect) |
|
double zoom = qMin( (double)viewport()->width() / (double)selRect.width(), (double)viewport()->height() / (double)selRect.height() ); |
|
double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)contentAreaWidth()); |
|
double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)contentAreaHeight()); |
|
|
|
const float upperZoomLimit = d->document->supportsTiles() ? 16.0 : 4.0; |
|
if ( d->zoomFactor <= upperZoomLimit || zoom <= 1.0 ) |
|
{ |
|
d->zoomFactor *= zoom; |
|
viewport()->setUpdatesEnabled( false ); |
|
updateZoom( ZoomRefreshCurrent ); |
|
viewport()->setUpdatesEnabled( true ); |
|
} |
|
|
|
// recenter view and update the viewport |
|
center( (int)(nX * contentAreaWidth()), (int)(nY * contentAreaHeight()) ); |
|
viewport()->update(); |
|
|
|
// hide message box and delete overlay window |
|
selectionClear(); |
|
} |
|
break; |
|
|
|
case Okular::Settings::EnumMouseMode::Magnifier: |
|
d->magnifierView->hide(); |
|
break; |
|
|
|
case Okular::Settings::EnumMouseMode::RectSelect: |
|
{ |
|
// if mouse is released and selection is null this is a rightClick |
|
if ( rightButton && !d->mouseSelecting ) |
|
{ |
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() ); |
|
break; |
|
} |
|
|
|
// if a selection is defined, display a popup |
|
if ( (!leftButton && !d->aPrevAction) || (!rightButton && d->aPrevAction) || |
|
!d->mouseSelecting ) |
|
break; |
|
|
|
QRect selectionRect = d->mouseSelectionRect.normalized(); |
|
if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 ) |
|
{ |
|
selectionClear(); |
|
if ( d->aPrevAction ) |
|
{ |
|
d->aPrevAction->trigger(); |
|
d->aPrevAction = 0; |
|
} |
|
break; |
|
} |
|
|
|
// if we support text generation |
|
QString selectedText; |
|
if (d->document->supportsSearching()) |
|
{ |
|
// grab text in selection by extracting it from all intersected pages |
|
const Okular::Page * okularPage=0; |
|
QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); |
|
for ( ; iIt != iEnd; ++iIt ) |
|
{ |
|
PageViewItem * item = *iIt; |
|
if ( !item->isVisible() ) |
|
continue; |
|
|
|
const QRect & itemRect = item->croppedGeometry(); |
|
if ( selectionRect.intersects( itemRect ) ) |
|
{ |
|
// request the textpage if there isn't one |
|
okularPage= item->page(); |
|
kWarning() << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage(); |
|
if ( !okularPage->hasTextPage() ) |
|
d->document->requestTextPage( okularPage->number() ); |
|
// grab text in the rect that intersects itemRect |
|
QRect relativeRect = selectionRect.intersect( itemRect ); |
|
relativeRect.translate( -item->uncroppedGeometry().topLeft() ); |
|
Okular::RegularAreaRect rects; |
|
rects.append( Okular::NormalizedRect( relativeRect, item->uncroppedWidth(), item->uncroppedHeight() ) ); |
|
selectedText += okularPage->text( &rects ); |
|
} |
|
} |
|
} |
|
|
|
// popup that ask to copy:text and copy/save:image |
|
KMenu menu( this ); |
|
QAction *textToClipboard = 0, *speakText = 0, *imageToClipboard = 0, *imageToFile = 0; |
|
if ( d->document->supportsSearching() && !selectedText.isEmpty() ) |
|
{ |
|
menu.addTitle( i18np( "Text (1 character)", "Text (%1 characters)", selectedText.length() ) ); |
|
textToClipboard = menu.addAction( KIcon("edit-copy"), i18n( "Copy to Clipboard" ) ); |
|
bool copyAllowed = d->document->isAllowed( Okular::AllowCopy ); |
|
if ( !copyAllowed ) |
|
{ |
|
textToClipboard->setEnabled( false ); |
|
textToClipboard->setText( i18n("Copy forbidden by DRM") ); |
|
} |
|
if ( Okular::Settings::useKTTSD() ) |
|
speakText = menu.addAction( KIcon("text-speak"), i18n( "Speak Text" ) ); |
|
if ( copyAllowed ) |
|
{ |
|
addWebShortcutsMenu( &menu, selectedText ); |
|
} |
|
} |
|
menu.addTitle( i18n( "Image (%1 by %2 pixels)", selectionRect.width(), selectionRect.height() ) ); |
|
imageToClipboard = menu.addAction( KIcon("image-x-generic"), i18n( "Copy to Clipboard" ) ); |
|
imageToFile = menu.addAction( KIcon("document-save"), i18n( "Save to File..." ) ); |
|
QAction *choice = menu.exec( e->globalPos() ); |
|
// check if the user really selected an action |
|
if ( choice ) |
|
{ |
|
// IMAGE operation chosen |
|
if ( choice == imageToClipboard || choice == imageToFile ) |
|
{ |
|
// renders page into a pixmap |
|
QPixmap copyPix( selectionRect.width(), selectionRect.height() ); |
|
QPainter copyPainter( ©Pix ); |
|
copyPainter.translate( -selectionRect.left(), -selectionRect.top() ); |
|
drawDocumentOnPainter( selectionRect, ©Painter ); |
|
copyPainter.end(); |
|
|
|
if ( choice == imageToClipboard ) |
|
{ |
|
// [2] copy pixmap to clipboard |
|
QClipboard *cb = QApplication::clipboard(); |
|
cb->setPixmap( copyPix, QClipboard::Clipboard ); |
|
if ( cb->supportsSelection() ) |
|
cb->setPixmap( copyPix, QClipboard::Selection ); |
|
d->messageWindow->display( i18n( "Image [%1x%2] copied to clipboard.", copyPix.width(), copyPix.height() ) ); |
|
} |
|
else if ( choice == imageToFile ) |
|
{ |
|
// [3] save pixmap to file |
|
QString fileName = KFileDialog::getSaveFileName( KUrl(), "image/png image/jpeg", this, QString(), |
|
KFileDialog::ConfirmOverwrite ); |
|
if ( fileName.isEmpty() ) |
|
d->messageWindow->display( i18n( "File not saved." ), QString(), PageViewMessage::Warning ); |
|
else |
|
{ |
|
KMimeType::Ptr mime = KMimeType::findByUrl( fileName ); |
|
QString type; |
|
if ( !mime || mime == KMimeType::defaultMimeTypePtr() ) |
|
type = "PNG"; |
|
else |
|
type = mime->name().section( '/', -1 ).toUpper(); |
|
copyPix.save( fileName, qPrintable( type ) ); |
|
d->messageWindow->display( i18n( "Image [%1x%2] saved to %3 file.", copyPix.width(), copyPix.height(), type ) ); |
|
} |
|
} |
|
} |
|
// TEXT operation chosen |
|
else |
|
{ |
|
if ( choice == textToClipboard ) |
|
{ |
|
// [1] copy text to clipboard |
|
QClipboard *cb = QApplication::clipboard(); |
|
cb->setText( selectedText, QClipboard::Clipboard ); |
|
if ( cb->supportsSelection() ) |
|
cb->setText( selectedText, QClipboard::Selection ); |
|
} |
|
else if ( choice == speakText ) |
|
{ |
|
// [2] speech selection using KTTSD |
|
d->tts()->say( selectedText ); |
|
} |
|
} |
|
} |
|
// clear widget selection and invalidate rect |
|
selectionClear(); |
|
|
|
// restore previous action if came from it using right button |
|
if ( d->aPrevAction ) |
|
{ |
|
d->aPrevAction->trigger(); |
|
d->aPrevAction = 0; |
|
} |
|
}break; |
|
|
|
case Okular::Settings::EnumMouseMode::TableSelect: |
|
{ |
|
// if mouse is released and selection is null this is a rightClick |
|
if ( rightButton && !d->mouseSelecting ) |
|
{ |
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() ); |
|
break; |
|
} |
|
|
|
QRect selectionRect = d->mouseSelectionRect.normalized(); |
|
if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 && d->tableSelectionParts.isEmpty() ) |
|
{ |
|
selectionClear(); |
|
if ( d->aPrevAction ) |
|
{ |
|
d->aPrevAction->trigger(); |
|
d->aPrevAction = 0; |
|
} |
|
break; |
|
} |
|
|
|
if (d->mouseSelecting) { |
|
// break up the selection into page-relative pieces |
|
d->tableSelectionParts.clear(); |
|
const Okular::Page * okularPage=0; |
|
QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); |
|
for ( ; iIt != iEnd; ++iIt ) |
|
{ |
|
PageViewItem * item = *iIt; |
|
if ( !item->isVisible() ) |
|
continue; |
|
|
|
const QRect & itemRect = item->croppedGeometry(); |
|
if ( selectionRect.intersects( itemRect ) ) |
|
{ |
|
// request the textpage if there isn't one |
|
okularPage= item->page(); |
|
kWarning() << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage(); |
|
if ( !okularPage->hasTextPage() ) |
|
d->document->requestTextPage( okularPage->number() ); |
|
// grab text in the rect that intersects itemRect |
|
QRect rectInItem = selectionRect.intersect( itemRect ); |
|
rectInItem.translate( -item->uncroppedGeometry().topLeft() ); |
|
QRect rectInSelection = selectionRect.intersect( itemRect ); |
|
rectInSelection.translate( -selectionRect.topLeft() ); |
|
d->tableSelectionParts.append( |
|
TableSelectionPart( |
|
item, |
|
Okular::NormalizedRect( rectInItem, item->uncroppedWidth(), item->uncroppedHeight() ), |
|
Okular::NormalizedRect( rectInSelection, selectionRect.width(), selectionRect.height() ) |
|
) |
|
); |
|
} |
|
} |
|
|
|
QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( 0, 0, 1, 1 ); |
|
updatedRect.translate( -contentAreaPosition() ); |
|
d->mouseSelecting = false; |
|
d->mouseSelectionRect.setCoords( 0, 0, 0, 0 ); |
|
d->tableSelectionCols.clear(); |
|
d->tableSelectionRows.clear(); |
|
guessTableDividers(); |
|
viewport()->update( updatedRect ); |
|
} |
|
|
|
if ( !d->document->isAllowed( Okular::AllowCopy ) ) { |
|
d->messageWindow->display( i18n("Copy forbidden by DRM"), QString(), PageViewMessage::Info, -1 ); |
|
break; |
|
} |
|
|
|
QString selText; |
|
QString selHtml; |
|
QList<double> xs = d->tableSelectionCols; |
|
QList<double> ys = d->tableSelectionRows; |
|
xs.prepend(0.0); |
|
xs.append(1.0); |
|
ys.prepend(0.0); |
|
ys.append(1.0); |
|
selHtml = "<html><head>" |
|
"<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">" |
|
"</head><body><table>"; |
|
for (int r=0; r+1<ys.length(); r++) { |
|
selHtml += "<tr>"; |
|
for (int c=0; c+1<xs.length(); c++) { |
|
Okular::NormalizedRect cell(xs[c], ys[r], xs[c+1], ys[r+1]); |
|
if (c) selText += '\t'; |
|
QString txt; |
|
foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { |
|
// first, crop the cell to this part |
|
if (!tsp.rectInSelection.intersects(cell)) |
|
continue; |
|
Okular::NormalizedRect cellPart = tsp.rectInSelection & cell; // intersection |
|
|
|
// second, convert it from table coordinates to part coordinates |
|
cellPart.left -= tsp.rectInSelection.left; |
|
cellPart.left /= (tsp.rectInSelection.right - tsp.rectInSelection.left); |
|
cellPart.right -= tsp.rectInSelection.left; |
|
cellPart.right /= (tsp.rectInSelection.right - tsp.rectInSelection.left); |
|
cellPart.top -= tsp.rectInSelection.top; |
|
cellPart.top /= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); |
|
cellPart.bottom -= tsp.rectInSelection.top; |
|
cellPart.bottom /= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); |
|
|
|
// third, convert from part coordinates to item coordinates |
|
cellPart.left *= (tsp.rectInItem.right - tsp.rectInItem.left); |
|
cellPart.left += tsp.rectInItem.left; |
|
cellPart.right *= (tsp.rectInItem.right - tsp.rectInItem.left); |
|
cellPart.right += tsp.rectInItem.left; |
|
cellPart.top *= (tsp.rectInItem.bottom - tsp.rectInItem.top); |
|
cellPart.top += tsp.rectInItem.top; |
|
cellPart.bottom *= (tsp.rectInItem.bottom - tsp.rectInItem.top); |
|
cellPart.bottom += tsp.rectInItem.top; |
|
|
|
// now get the text |
|
Okular::RegularAreaRect rects; |
|
rects.append( cellPart ); |
|
txt += tsp.item->page()->text( &rects, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ); |
|
} |
|
QString html = txt; |
|
selText += txt.replace('\n', ' '); |
|
html.replace('&', "&").replace('<', "<").replace('>', ">"); |
|
// Remove newlines, do not turn them into <br>, because |
|
// Excel interprets <br> within cell as new cell... |
|
html.replace('\n', " "); |
|
selHtml += "<td>"+html+"</td>"; |
|
} |
|
selText += '\n'; |
|
selHtml += "</tr>\n"; |
|
} |
|
selHtml += "</table></body></html>\n"; |
|
|
|
|
|
QClipboard *cb = QApplication::clipboard(); |
|
QMimeData *md = new QMimeData(); |
|
md->setText(selText); |
|
md->setHtml(selHtml); |
|
cb->setMimeData( md, QClipboard::Clipboard ); |
|
if ( cb->supportsSelection() ) |
|
cb->setMimeData( md, QClipboard::Selection ); |
|
|
|
}break; |
|
case Okular::Settings::EnumMouseMode::TextSelect: |
|
if ( d->mouseTextSelecting ) |
|
{ |
|
d->mouseTextSelecting = false; |
|
// textSelectionClear(); |
|
if ( d->document->isAllowed( Okular::AllowCopy ) ) |
|
{ |
|
const QString text = d->selectedText(); |
|
if ( !text.isEmpty() ) |
|
{ |
|
QClipboard *cb = QApplication::clipboard(); |
|
if ( cb->supportsSelection() ) |
|
cb->setText( text, QClipboard::Selection ); |
|
} |
|
} |
|
} |
|
else if ( !d->mousePressPos.isNull() && rightButton ) |
|
{ |
|
PageViewItem* item = pickItemOnPoint(eventPos.x(),eventPos.y()); |
|
const Okular::Page *page; |
|
//if there is text selected in the page |
|
if (item && (page = item->page())->textSelection()) |
|
{ |
|
KMenu menu( this ); |
|
QAction *textToClipboard = menu.addAction( KIcon( "edit-copy" ), i18n( "Copy Text" ) ); |
|
QAction *speakText = 0; |
|
QAction *httpLink = 0; |
|
if ( Okular::Settings::useKTTSD() ) |
|
speakText = menu.addAction( KIcon( "text-speak" ), i18n( "Speak Text" ) ); |
|
if ( !d->document->isAllowed( Okular::AllowCopy ) ) |
|
{ |
|
textToClipboard->setEnabled( false ); |
|
textToClipboard->setText( i18n("Copy forbidden by DRM") ); |
|
} |
|
else |
|
{ |
|
addWebShortcutsMenu( &menu, d->selectedText() ); |
|
} |
|
const QString url = UrlUtils::getUrl( d->selectedText() ); |
|
if ( !url.isEmpty() ) |
|
{ |
|
const QString squeezedText = KStringHandler::rsqueeze( url, 30 ); |
|
httpLink = menu.addAction( i18n( "Go to '%1'", squeezedText ) ); |
|
} |
|
QAction *choice = menu.exec( e->globalPos() ); |
|
// check if the user really selected an action |
|
if ( choice ) |
|
{ |
|
if ( choice == textToClipboard ) |
|
copyTextSelection(); |
|
else if ( choice == speakText ) |
|
{ |
|
const QString text = d->selectedText(); |
|
d->tts()->say( text ); |
|
} |
|
else if ( choice == httpLink ) |
|
new KRun( KUrl( url ), this ); |
|
} |
|
} |
|
} |
|
break; |
|
} |
|
|
|
// reset mouse press / 'drag start' position |
|
d->mousePressPos = QPoint(); |
|
} |
|
|
|
void PageView::guessTableDividers() |
|
{ |
|
QList< QPair<double, int> > colTicks, rowTicks, colSelectionTicks, rowSelectionTicks; |
|
|
|
foreach ( const TableSelectionPart& tsp, d->tableSelectionParts ) |
|
{ |
|
// add ticks for the edges of this area... |
|
colSelectionTicks.append( qMakePair( tsp.rectInSelection.left, +1 ) ); |
|
colSelectionTicks.append( qMakePair( tsp.rectInSelection.right, -1 ) ); |
|
rowSelectionTicks.append( qMakePair( tsp.rectInSelection.top, +1 ) ); |
|
rowSelectionTicks.append( qMakePair( tsp.rectInSelection.bottom, -1 ) ); |
|
|
|
// get the words in this part |
|
Okular::RegularAreaRect rects; |
|
rects.append( tsp.rectInItem ); |
|
const Okular::TextEntity::List words = tsp.item->page()->words( &rects, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ); |
|
|
|
foreach (Okular::TextEntity *te, words) |
|
{ |
|
if (te->text().isEmpty()) { |
|
delete te; |
|
continue; |
|
} |
|
|
|
Okular::NormalizedRect wordArea = *te->area(); |
|
|
|
// convert it from item coordinates to part coordinates |
|
wordArea.left -= tsp.rectInItem.left; |
|
wordArea.left /= (tsp.rectInItem.right - tsp.rectInItem.left); |
|
wordArea.right -= tsp.rectInItem.left; |
|
wordArea.right /= (tsp.rectInItem.right - tsp.rectInItem.left); |
|
wordArea.top -= tsp.rectInItem.top; |
|
wordArea.top /= (tsp.rectInItem.bottom - tsp.rectInItem.top); |
|
wordArea.bottom -= tsp.rectInItem.top; |
|
wordArea.bottom /= (tsp.rectInItem.bottom - tsp.rectInItem.top); |
|
|
|
// convert from part coordinates to table coordinates |
|
wordArea.left *= (tsp.rectInSelection.right - tsp.rectInSelection.left); |
|
wordArea.left += tsp.rectInSelection.left; |
|
wordArea.right *= (tsp.rectInSelection.right - tsp.rectInSelection.left); |
|
wordArea.right += tsp.rectInSelection.left; |
|
wordArea.top *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); |
|
wordArea.top += tsp.rectInSelection.top; |
|
wordArea.bottom *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); |
|
wordArea.bottom += tsp.rectInSelection.top; |
|
|
|
// add to the ticks arrays... |
|
colTicks.append( qMakePair( wordArea.left, +1) ); |
|
colTicks.append( qMakePair( wordArea.right, -1) ); |
|
rowTicks.append( qMakePair( wordArea.top, +1) ); |
|
rowTicks.append( qMakePair( wordArea.bottom, -1) ); |
|
|
|
delete te; |
|
} |
|
} |
|
|
|
int tally = 0; |
|
|
|
qSort( colSelectionTicks ); |
|
qSort( rowSelectionTicks ); |
|
|
|
for (int i = 0; i < colSelectionTicks.length(); ++i) |
|
{ |
|
tally += colSelectionTicks[i].second; |
|
if ( tally == 0 && i + 1 < colSelectionTicks.length() && colSelectionTicks[i+1].first != colSelectionTicks[i].first) |
|
{ |
|
colTicks.append( qMakePair( colSelectionTicks[i].first, +1 ) ); |
|
colTicks.append( qMakePair( colSelectionTicks[i+1].first, -1 ) ); |
|
} |
|
} |
|
Q_ASSERT( tally == 0 ); |
|
|
|
for (int i = 0; i < rowSelectionTicks.length(); ++i) |
|
{ |
|
tally += rowSelectionTicks[i].second; |
|
if ( tally == 0 && i + 1 < rowSelectionTicks.length() && rowSelectionTicks[i+1].first != rowSelectionTicks[i].first) { |
|
rowTicks.append( qMakePair( rowSelectionTicks[i].first, +1 ) ); |
|
rowTicks.append( qMakePair( rowSelectionTicks[i+1].first, -1 ) ); |
|
} |
|
} |
|
Q_ASSERT( tally == 0 ); |
|
|
|
qSort( colTicks ); |
|
qSort( rowTicks ); |
|
|
|
for (int i = 0; i < colTicks.length(); ++i) |
|
{ |
|
tally += colTicks[i].second; |
|
if ( tally == 0 && i + 1 < colTicks.length() && colTicks[i+1].first != colTicks[i].first) |
|
{ |
|
d->tableSelectionCols.append( (colTicks[i].first+colTicks[i+1].first) / 2 ); |
|
d->tableDividersGuessed = true; |
|
} |
|
} |
|
Q_ASSERT( tally == 0 ); |
|
|
|
for (int i = 0; i < rowTicks.length(); ++i) |
|
{ |
|
tally += rowTicks[i].second; |
|
if ( tally == 0 && i + 1 < rowTicks.length() && rowTicks[i+1].first != rowTicks[i].first) |
|
{ |
|
d->tableSelectionRows.append( (rowTicks[i].first+rowTicks[i+1].first) / 2 ); |
|
d->tableDividersGuessed = true; |
|
} |
|
} |
|
Q_ASSERT( tally == 0 ); |
|
} |
|
|
|
void PageView::mouseDoubleClickEvent( QMouseEvent * e ) |
|
{ |
|
if ( e->button() == Qt::LeftButton ) |
|
{ |
|
const QPoint eventPos = contentAreaPoint( e->pos() ); |
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
if ( pageItem ) |
|
{ |
|
// find out normalized mouse coords inside current item |
|
double nX = pageItem->absToPageX(eventPos.x()); |
|
double nY = pageItem->absToPageY(eventPos.y()); |
|
|
|
if ( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TextSelect ) { |
|
textSelectionClear(); |
|
|
|
Okular::RegularAreaRect *wordRect = pageItem->page()->wordAt( Okular::NormalizedPoint( nX, nY ) ); |
|
if ( wordRect ) |
|
{ |
|
// TODO words with hyphens across pages |
|
d->document->setPageTextSelection( pageItem->pageNumber(), wordRect, palette().color( QPalette::Active, QPalette::Highlight ) ); |
|
d->pagesWithTextSelection << pageItem->pageNumber(); |
|
if ( d->document->isAllowed( Okular::AllowCopy ) ) |
|
{ |
|
const QString text = d->selectedText(); |
|
if ( !text.isEmpty() ) |
|
{ |
|
QClipboard *cb = QApplication::clipboard(); |
|
if ( cb->supportsSelection() ) |
|
cb->setText( text, QClipboard::Selection ); |
|
} |
|
} |
|
return; |
|
} |
|
} |
|
|
|
const QRect & itemRect = pageItem->uncroppedGeometry(); |
|
Okular::Annotation * ann = 0; |
|
const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() ); |
|
if ( orect ) |
|
ann = ( (Okular::AnnotationObjectRect *)orect )->annotation(); |
|
if ( ann && ann->subType() != Okular::Annotation::AWidget ) |
|
{ |
|
openAnnotationWindow( ann, pageItem->pageNumber() ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void PageView::wheelEvent( QWheelEvent *e ) |
|
{ |
|
// don't perform any mouse action when viewport is autoscrolling |
|
if ( d->viewportMoveActive ) |
|
return; |
|
|
|
if ( !d->document->isOpened() ) |
|
{ |
|
QAbstractScrollArea::wheelEvent( e ); |
|
return; |
|
} |
|
|
|
int delta = e->delta(), |
|
vScroll = verticalScrollBar()->value(); |
|
e->accept(); |
|
if ( (e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) { |
|
if ( e->delta() < 0 ) |
|
slotZoomOut(); |
|
else |
|
slotZoomIn(); |
|
} |
|
else if ( delta <= -120 && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->maximum() ) |
|
{ |
|
// go to next page |
|
if ( (int)d->document->currentPage() < d->items.count() - 1 ) |
|
{ |
|
// more optimized than document->setNextPage and then move view to top |
|
Okular::DocumentViewport newViewport = d->document->viewport(); |
|
newViewport.pageNumber += viewColumns(); |
|
if ( newViewport.pageNumber >= (int)d->items.count() ) |
|
newViewport.pageNumber = d->items.count() - 1; |
|
newViewport.rePos.enabled = true; |
|
newViewport.rePos.normalizedY = 0.0; |
|
d->document->setViewport( newViewport ); |
|
} |
|
} |
|
else if ( delta >= 120 && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->minimum() ) |
|
{ |
|
// go to prev page |
|
if ( d->document->currentPage() > 0 ) |
|
{ |
|
// more optimized than document->setPrevPage and then move view to bottom |
|
Okular::DocumentViewport newViewport = d->document->viewport(); |
|
newViewport.pageNumber -= viewColumns(); |
|
if ( newViewport.pageNumber < 0 ) |
|
newViewport.pageNumber = 0; |
|
newViewport.rePos.enabled = true; |
|
newViewport.rePos.normalizedY = 1.0; |
|
d->document->setViewport( newViewport ); |
|
} |
|
} |
|
else |
|
QAbstractScrollArea::wheelEvent( e ); |
|
|
|
updateCursor(); |
|
} |
|
|
|
bool PageView::viewportEvent( QEvent * e ) |
|
{ |
|
if ( e->type() == QEvent::ToolTip && Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Browse ) |
|
{ |
|
QHelpEvent * he = static_cast< QHelpEvent* >( e ); |
|
const QPoint eventPos = contentAreaPoint( he->pos() ); |
|
PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); |
|
const Okular::ObjectRect * rect = 0; |
|
const Okular::Action * link = 0; |
|
const Okular::Annotation * ann = 0; |
|
if ( pageItem ) |
|
{ |
|
double nX = pageItem->absToPageX( eventPos.x() ); |
|
double nY = pageItem->absToPageY( eventPos.y() ); |
|
rect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
if ( rect ) |
|
ann = static_cast< const Okular::AnnotationObjectRect * >( rect )->annotation(); |
|
else |
|
{ |
|
rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
if ( rect ) |
|
link = static_cast< const Okular::Action * >( rect->object() ); |
|
} |
|
} |
|
|
|
if ( ann && ann->subType() != Okular::Annotation::AWidget ) |
|
{ |
|
QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
r.translate( pageItem->uncroppedGeometry().topLeft() ); |
|
r.translate( -contentAreaPosition() ); |
|
QString tip = GuiUtils::prettyToolTip( ann ); |
|
QToolTip::showText( he->globalPos(), tip, viewport(), r ); |
|
} |
|
else if ( link ) |
|
{ |
|
QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
r.translate( pageItem->uncroppedGeometry().topLeft() ); |
|
r.translate( -contentAreaPosition() ); |
|
QString tip = link->actionTip(); |
|
if ( !tip.isEmpty() ) |
|
QToolTip::showText( he->globalPos(), tip, viewport(), r ); |
|
} |
|
e->accept(); |
|
return true; |
|
} |
|
else |
|
// do not stop the event |
|
return QAbstractScrollArea::viewportEvent( e ); |
|
} |
|
|
|
void PageView::scrollContentsBy( int dx, int dy ) |
|
{ |
|
const QRect r = viewport()->rect(); |
|
viewport()->scroll( dx, dy, r ); |
|
// HACK manually repaint the damaged regions, as it seems some updates are missed |
|
// thus leaving artifacts around |
|
QRegion rgn( r ); |
|
rgn -= rgn & r.translated( dx, dy ); |
|
foreach ( const QRect &rect, rgn.rects() ) |
|
viewport()->repaint( rect ); |
|
} |
|
//END widget events |
|
|
|
QList< Okular::RegularAreaRect * > PageView::textSelections( const QPoint& start, const QPoint& end, int& firstpage ) |
|
{ |
|
firstpage = -1; |
|
QList< Okular::RegularAreaRect * > ret; |
|
QSet< int > affectedItemsSet; |
|
QRect selectionRect = QRect( start, end ).normalized(); |
|
foreach( PageViewItem * item, d->items ) |
|
{ |
|
if ( item->isVisible() && selectionRect.intersects( item->croppedGeometry() ) ) |
|
affectedItemsSet.insert( item->pageNumber() ); |
|
} |
|
#ifdef PAGEVIEW_DEBUG |
|
kDebug() << ">>>> item selected by mouse:" << affectedItemsSet.count(); |
|
#endif |
|
|
|
if ( !affectedItemsSet.isEmpty() ) |
|
{ |
|
// is the mouse drag line the ne-sw diagonal of the selection rect? |
|
bool direction_ne_sw = start == selectionRect.topRight() || start == selectionRect.bottomLeft(); |
|
|
|
int tmpmin = d->document->pages(); |
|
int tmpmax = 0; |
|
foreach( int p, affectedItemsSet ) |
|
{ |
|
if ( p < tmpmin ) tmpmin = p; |
|
if ( p > tmpmax ) tmpmax = p; |
|
} |
|
|
|
PageViewItem * a = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.right() : selectionRect.left() ), (int)selectionRect.top() ); |
|
int min = a && ( a->pageNumber() != tmpmax ) ? a->pageNumber() : tmpmin; |
|
PageViewItem * b = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.left() : selectionRect.right() ), (int)selectionRect.bottom() ); |
|
int max = b && ( b->pageNumber() != tmpmin ) ? b->pageNumber() : tmpmax; |
|
|
|
QList< int > affectedItemsIds; |
|
for ( int i = min; i <= max; ++i ) |
|
affectedItemsIds.append( i ); |
|
#ifdef PAGEVIEW_DEBUG |
|
kDebug() << ">>>> pages:" << affectedItemsIds; |
|
#endif |
|
firstpage = affectedItemsIds.first(); |
|
|
|
if ( affectedItemsIds.count() == 1 ) |
|
{ |
|
PageViewItem * item = d->items[ affectedItemsIds.first() ]; |
|
selectionRect.translate( -item->uncroppedGeometry().topLeft() ); |
|
ret.append( textSelectionForItem( item, |
|
direction_ne_sw ? selectionRect.topRight() : selectionRect.topLeft(), |
|
direction_ne_sw ? selectionRect.bottomLeft() : selectionRect.bottomRight() ) ); |
|
} |
|
else if ( affectedItemsIds.count() > 1 ) |
|
{ |
|
// first item |
|
PageViewItem * first = d->items[ affectedItemsIds.first() ]; |
|
QRect geom = first->croppedGeometry().intersect( selectionRect ).translated( -first->uncroppedGeometry().topLeft() ); |
|
ret.append( textSelectionForItem( first, |
|
selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.topRight() : geom.topLeft() ) : ( direction_ne_sw ? geom.bottomRight() : geom.bottomLeft() ), |
|
QPoint() ) ); |
|
// last item |
|
PageViewItem * last = d->items[ affectedItemsIds.last() ]; |
|
geom = last->croppedGeometry().intersect( selectionRect ).translated( -last->uncroppedGeometry().topLeft() ); |
|
// the last item needs to appended at last... |
|
Okular::RegularAreaRect * lastArea = textSelectionForItem( last, |
|
QPoint(), |
|
selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.bottomLeft() : geom.bottomRight() ) : ( direction_ne_sw ? geom.topLeft() : geom.topRight() ) ); |
|
affectedItemsIds.removeFirst(); |
|
affectedItemsIds.removeLast(); |
|
// item between the two above |
|
foreach( int page, affectedItemsIds ) |
|
{ |
|
ret.append( textSelectionForItem( d->items[ page ] ) ); |
|
} |
|
ret.append( lastArea ); |
|
} |
|
} |
|
return ret; |
|
} |
|
|
|
|
|
void PageView::drawDocumentOnPainter( const QRect & contentsRect, QPainter * p ) |
|
{ |
|
QColor backColor = viewport()->palette().color( QPalette::Dark ); |
|
|
|
// when checking if an Item is contained in contentsRect, instead of |
|
// growing PageViewItems rects (for keeping outline into account), we |
|
// grow the contentsRect |
|
QRect checkRect = contentsRect; |
|
checkRect.adjust( -3, -3, 1, 1 ); |
|
|
|
// create a region from which we'll subtract painted rects |
|
QRegion remainingArea( contentsRect ); |
|
|
|
// iterate over all items painting the ones intersecting contentsRect |
|
QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); |
|
for ( ; iIt != iEnd; ++iIt ) |
|
{ |
|
// check if a piece of the page intersects the contents rect |
|
if ( !(*iIt)->isVisible() || !(*iIt)->croppedGeometry().intersects( checkRect ) ) |
|
continue; |
|
|
|
// get item and item's outline geometries |
|
PageViewItem * item = *iIt; |
|
QRect itemGeometry = item->croppedGeometry(), |
|
outlineGeometry = itemGeometry; |
|
outlineGeometry.adjust( -1, -1, 3, 3 ); |
|
|
|
// move the painter to the top-left corner of the real page |
|
p->save(); |
|
p->translate( itemGeometry.left(), itemGeometry.top() ); |
|
|
|
// draw the page outline (black border and 2px bottom-right shadow) |
|
if ( !itemGeometry.contains( contentsRect ) ) |
|
{ |
|
int itemWidth = itemGeometry.width(), |
|
itemHeight = itemGeometry.height(); |
|
// draw simple outline |
|
p->setPen( Qt::black ); |
|
p->drawRect( -1, -1, itemWidth + 1, itemHeight + 1 ); |
|
// draw bottom/right gradient |
|
static int levels = 2; |
|
int r = backColor.red() / (levels + 2) + 6, |
|
g = backColor.green() / (levels + 2) + 6, |
|
b = backColor.blue() / (levels + 2) + 6; |
|
for ( int i = 0; i < levels; i++ ) |
|
{ |
|
p->setPen( QColor( r * (i+2), g * (i+2), b * (i+2) ) ); |
|
p->drawLine( i, i + itemHeight + 1, i + itemWidth + 1, i + itemHeight + 1 ); |
|
p->drawLine( i + itemWidth + 1, i, i + itemWidth + 1, i + itemHeight ); |
|
p->setPen( backColor ); |
|
p->drawLine( -1, i + itemHeight + 1, i - 1, i + itemHeight + 1 ); |
|
p->drawLine( i + itemWidth + 1, -1, i + itemWidth + 1, i - 1 ); |
|
} |
|
} |
|
|
|
// draw the page using the PagePainter with all flags active |
|
if ( contentsRect.intersects( itemGeometry ) ) |
|
{ |
|
Okular::NormalizedPoint *viewPortPoint = 0; |
|
Okular::NormalizedPoint point( d->lastSourceLocationViewportNormalizedX, d->lastSourceLocationViewportNormalizedY ); |
|
if( Okular::Settings::showSourceLocationsGraphically() |
|
&& item->pageNumber() == d->lastSourceLocationViewportPageNumber ) |
|
{ |
|
viewPortPoint = &point; |
|
} |
|
QRect pixmapRect = contentsRect.intersect( itemGeometry ); |
|
pixmapRect.translate( -item->croppedGeometry().topLeft() ); |
|
PagePainter::paintCroppedPageOnPainter( p, item->page(), this, pageflags, |
|
item->uncroppedWidth(), item->uncroppedHeight(), pixmapRect, |
|
item->crop(), viewPortPoint ); |
|
} |
|
|
|
// remove painted area from 'remainingArea' and restore painter |
|
remainingArea -= outlineGeometry.intersect( contentsRect ); |
|
p->restore(); |
|
} |
|
|
|
// fill with background color the unpainted area |
|
const QVector<QRect> &backRects = remainingArea.rects(); |
|
int backRectsNumber = backRects.count(); |
|
for ( int jr = 0; jr < backRectsNumber; jr++ ) |
|
p->fillRect( backRects[ jr ], backColor ); |
|
} |
|
|
|
void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight ) |
|
{ |
|
const Okular::Page * okularPage = item->page(); |
|
double width = okularPage->width(), |
|
height = okularPage->height(), |
|
zoom = d->zoomFactor; |
|
Okular::NormalizedRect crop( 0., 0., 1., 1. ); |
|
|
|
// Handle cropping |
|
if ( Okular::Settings::trimMargins() && okularPage->isBoundingBoxKnown() |
|
&& !okularPage->boundingBox().isNull() ) |
|
{ |
|
crop = okularPage->boundingBox(); |
|
|
|
// Rotate the bounding box |
|
for ( int i = okularPage->rotation(); i > 0; --i ) |
|
{ |
|
Okular::NormalizedRect rot = crop; |
|
crop.left = 1 - rot.bottom; |
|
crop.top = rot.left; |
|
crop.right = 1 - rot.top; |
|
crop.bottom = rot.right; |
|
} |
|
|
|
// Expand the crop slightly beyond the bounding box |
|
static const double cropExpandRatio = 0.04; |
|
const double cropExpand = cropExpandRatio * ( (crop.right-crop.left) + (crop.bottom-crop.top) ) / 2; |
|
crop = Okular::NormalizedRect( |
|
crop.left - cropExpand, |
|
crop.top - cropExpand, |
|
crop.right + cropExpand, |
|
crop.bottom + cropExpand ) & Okular::NormalizedRect( 0, 0, 1, 1 ); |
|
|
|
// We currently generate a larger image and then crop it, so if the |
|
// crop rect is very small the generated image is huge. Hence, we shouldn't |
|
// let the crop rect become too small. |
|
// Make sure we crop by at most 50% in either dimension: |
|
static const double minCropRatio = 0.5; |
|
if ( ( crop.right - crop.left ) < minCropRatio ) |
|
{ |
|
const double newLeft = ( crop.left + crop.right ) / 2 - minCropRatio/2; |
|
crop.left = qMax( 0.0, qMin( 1.0 - minCropRatio, newLeft ) ); |
|
crop.right = crop.left + minCropRatio; |
|
} |
|
if ( ( crop.bottom - crop.top ) < minCropRatio ) |
|
{ |
|
const double newTop = ( crop.top + crop.bottom ) / 2 - minCropRatio/2; |
|
crop.top = qMax( 0.0, qMin( 1.0 - minCropRatio, newTop ) ); |
|
crop.bottom = crop.top + minCropRatio; |
|
} |
|
|
|
width *= ( crop.right - crop.left ); |
|
height *= ( crop.bottom - crop.top ); |
|
#ifdef PAGEVIEW_DEBUG |
|
kDebug() << "Cropped page" << okularPage->number() << "to" << crop |
|
<< "width" << width << "height" << height << "by bbox" << okularPage->boundingBox(); |
|
#endif |
|
} |
|
|
|
if ( d->zoomMode == ZoomFixed ) |
|
{ |
|
width *= zoom; |
|
height *= zoom; |
|
item->setWHZC( (int)width, (int)height, d->zoomFactor, crop ); |
|
} |
|
else if ( d->zoomMode == ZoomFitWidth ) |
|
{ |
|
height = ( height / width ) * colWidth; |
|
zoom = (double)colWidth / width; |
|
item->setWHZC( colWidth, (int)height, zoom, crop ); |
|
d->zoomFactor = zoom; |
|
} |
|
else if ( d->zoomMode == ZoomFitPage ) |
|
{ |
|
const double scaleW = (double)colWidth / (double)width; |
|
const double scaleH = (double)rowHeight / (double)height; |
|
zoom = qMin( scaleW, scaleH ); |
|
item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop ); |
|
d->zoomFactor = zoom; |
|
} |
|
else if ( d->zoomMode == ZoomFitAuto ) |
|
{ |
|
const double aspectRatioRelation = 1.25; // relation between aspect ratios for "auto fit" |
|
const double uiAspect = (double)rowHeight / (double)colWidth; |
|
const double pageAspect = (double)height / (double)width; |
|
const double rel = uiAspect / pageAspect; |
|
|
|
const bool isContinuous = Okular::Settings::viewContinuous(); |
|
if ( !isContinuous && rel > aspectRatioRelation ) |
|
{ |
|
// UI space is relatively much higher than the page |
|
zoom = (double)rowHeight / (double)height; |
|
} |
|
else if ( rel < 1.0 / aspectRatioRelation ) |
|
{ |
|
// UI space is relatively much wider than the page in relation |
|
zoom = (double)colWidth / (double)width; |
|
} |
|
else |
|
{ |
|
// aspect ratios of page and UI space are very similar |
|
const double scaleW = (double)colWidth / (double)width; |
|
const double scaleH = (double)rowHeight / (double)height; |
|
zoom = qMin( scaleW, scaleH ); |
|
} |
|
item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop ); |
|
d->zoomFactor = zoom; |
|
} |
|
#ifndef NDEBUG |
|
else |
|
kDebug() << "calling updateItemSize with unrecognized d->zoomMode!"; |
|
#endif |
|
} |
|
|
|
PageViewItem * PageView::pickItemOnPoint( int x, int y ) |
|
{ |
|
PageViewItem * item = 0; |
|
QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); |
|
for ( ; iIt != iEnd; ++iIt ) |
|
{ |
|
PageViewItem * i = *iIt; |
|
const QRect & r = i->croppedGeometry(); |
|
if ( x < r.right() && x > r.left() && y < r.bottom() ) |
|
{ |
|
if ( y > r.top() ) |
|
item = i; |
|
break; |
|
} |
|
} |
|
return item; |
|
} |
|
|
|
void PageView::textSelectionClear() |
|
{ |
|
// something to clear |
|
if ( !d->pagesWithTextSelection.isEmpty() ) |
|
{ |
|
QSet< int >::ConstIterator it = d->pagesWithTextSelection.constBegin(), itEnd = d->pagesWithTextSelection.constEnd(); |
|
for ( ; it != itEnd; ++it ) |
|
d->document->setPageTextSelection( *it, 0, QColor() ); |
|
d->pagesWithTextSelection.clear(); |
|
} |
|
} |
|
|
|
void PageView::selectionStart( const QPoint & pos, const QColor & color, bool /*aboveAll*/ ) |
|
{ |
|
selectionClear(); |
|
d->mouseSelecting = true; |
|
d->mouseSelectionRect.setRect( pos.x(), pos.y(), 1, 1 ); |
|
d->mouseSelectionColor = color; |
|
// ensures page doesn't scroll |
|
if ( d->autoScrollTimer ) |
|
{ |
|
d->scrollIncrement = 0; |
|
d->autoScrollTimer->stop(); |
|
} |
|
} |
|
|
|
void PageView::scrollPosIntoView( const QPoint & pos ) |
|
{ |
|
if (pos.x() < horizontalScrollBar()->value()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value()); |
|
else if (horizontalScrollBar()->value() + viewport()->width() < pos.x()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value() - viewport()->width()); |
|
else d->dragScrollVector.setX(0); |
|
|
|
if (pos.y() < verticalScrollBar()->value()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value()); |
|
else if (verticalScrollBar()->value() + viewport()->height() < pos.y()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value() - viewport()->height()); |
|
else d->dragScrollVector.setY(0); |
|
|
|
if (d->dragScrollVector != QPoint(0, 0)) |
|
{ |
|
if (!d->dragScrollTimer.isActive()) d->dragScrollTimer.start(100); |
|
} |
|
else d->dragScrollTimer.stop(); |
|
} |
|
|
|
void PageView::updateSelection( const QPoint & pos ) |
|
{ |
|
if ( d->mouseSelecting ) |
|
{ |
|
scrollPosIntoView( pos ); |
|
// update the selection rect |
|
QRect updateRect = d->mouseSelectionRect; |
|
d->mouseSelectionRect.setBottomLeft( pos ); |
|
updateRect |= d->mouseSelectionRect; |
|
updateRect.translate( -contentAreaPosition() ); |
|
viewport()->update( updateRect.adjusted( -1, -1, 1, 1 ) ); |
|
} |
|
else if ( d->mouseTextSelecting) |
|
{ |
|
scrollPosIntoView( pos ); |
|
int first = -1; |
|
const QList< Okular::RegularAreaRect * > selections = textSelections( pos, d->mouseSelectPos, first ); |
|
QSet< int > pagesWithSelectionSet; |
|
for ( int i = 0; i < selections.count(); ++i ) |
|
pagesWithSelectionSet.insert( i + first ); |
|
|
|
const QSet< int > noMoreSelectedPages = d->pagesWithTextSelection - pagesWithSelectionSet; |
|
// clear the selection from pages not selected anymore |
|
foreach( int p, noMoreSelectedPages ) |
|
{ |
|
d->document->setPageTextSelection( p, 0, QColor() ); |
|
} |
|
// set the new selection for the selected pages |
|
foreach( int p, pagesWithSelectionSet ) |
|
{ |
|
d->document->setPageTextSelection( p, selections[ p - first ], palette().color( QPalette::Active, QPalette::Highlight ) ); |
|
} |
|
d->pagesWithTextSelection = pagesWithSelectionSet; |
|
} |
|
} |
|
|
|
static Okular::NormalizedPoint rotateInNormRect( const QPoint &rotated, const QRect &rect, Okular::Rotation rotation ) |
|
{ |
|
Okular::NormalizedPoint ret; |
|
|
|
switch ( rotation ) |
|
{ |
|
case Okular::Rotation0: |
|
ret = Okular::NormalizedPoint( rotated.x(), rotated.y(), rect.width(), rect.height() ); |
|
break; |
|
case Okular::Rotation90: |
|
ret = Okular::NormalizedPoint( rotated.y(), rect.width() - rotated.x(), rect.height(), rect.width() ); |
|
break; |
|
case Okular::Rotation180: |
|
ret = Okular::NormalizedPoint( rect.width() - rotated.x(), rect.height() - rotated.y(), rect.width(), rect.height() ); |
|
break; |
|
case Okular::Rotation270: |
|
ret = Okular::NormalizedPoint( rect.height() - rotated.y(), rotated.x(), rect.height(), rect.width() ); |
|
break; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
Okular::RegularAreaRect * PageView::textSelectionForItem( PageViewItem * item, const QPoint & startPoint, const QPoint & endPoint ) |
|
{ |
|
const QRect & geometry = item->uncroppedGeometry(); |
|
Okular::NormalizedPoint startCursor( 0.0, 0.0 ); |
|
if ( !startPoint.isNull() ) |
|
{ |
|
startCursor = rotateInNormRect( startPoint, geometry, item->page()->rotation() ); |
|
} |
|
Okular::NormalizedPoint endCursor( 1.0, 1.0 ); |
|
if ( !endPoint.isNull() ) |
|
{ |
|
endCursor = rotateInNormRect( endPoint, geometry, item->page()->rotation() ); |
|
} |
|
Okular::TextSelection mouseTextSelectionInfo( startCursor, endCursor ); |
|
|
|
const Okular::Page * okularPage = item->page(); |
|
|
|
if ( !okularPage->hasTextPage() ) |
|
d->document->requestTextPage( okularPage->number() ); |
|
|
|
Okular::RegularAreaRect * selectionArea = okularPage->textArea( &mouseTextSelectionInfo ); |
|
#ifdef PAGEVIEW_DEBUG |
|
kDebug().nospace() << "text areas (" << okularPage->number() << "): " << ( selectionArea ? QString::number( selectionArea->count() ) : "(none)" ); |
|
#endif |
|
return selectionArea; |
|
} |
|
|
|
void PageView::selectionClear(const ClearMode mode) |
|
{ |
|
QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( 0, 0, 1, 1 ); |
|
d->mouseSelecting = false; |
|
d->mouseSelectionRect.setCoords( 0, 0, 0, 0 ); |
|
d->tableSelectionCols.clear(); |
|
d->tableSelectionRows.clear(); |
|
d->tableDividersGuessed = false; |
|
foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { |
|
QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); |
|
selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); |
|
// should check whether this is on-screen here? |
|
updatedRect = updatedRect.united(selectionPartRect); |
|
} |
|
if ( mode != ClearOnlyDividers ) { |
|
d->tableSelectionParts.clear(); |
|
} |
|
d->tableSelectionParts.clear(); |
|
updatedRect.translate( -contentAreaPosition() ); |
|
viewport()->update( updatedRect ); |
|
} |
|
|
|
// const to be used for both zoomFactorFitMode function and slotRelayoutPages. |
|
static const int kcolWidthMargin = 6; |
|
static const int krowHeightMargin = 12; |
|
|
|
double PageView::zoomFactorFitMode( ZoomMode mode ) |
|
{ |
|
const int pageCount = d->items.count(); |
|
if ( pageCount == 0 ) |
|
return 0; |
|
const bool facingCentered = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered || (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount == 1); |
|
const bool overrideCentering = facingCentered && pageCount < 3; |
|
const int nCols = overrideCentering ? 1 : viewColumns(); |
|
const double colWidth = viewport()->width() / nCols - kcolWidthMargin; |
|
const double rowHeight = viewport()->height() - krowHeightMargin; |
|
const PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage()) ]; |
|
// prevent segmentation fault when openning a new document; |
|
if ( !currentItem ) |
|
return 0; |
|
const Okular::Page * okularPage = currentItem->page(); |
|
const double width = okularPage->width(), height = okularPage->height(); |
|
if ( mode == ZoomFitWidth ) |
|
return (double) colWidth / width; |
|
if ( mode == ZoomFitPage ) |
|
{ |
|
const double scaleW = (double) colWidth / (double)width; |
|
const double scaleH = (double) rowHeight / (double)height; |
|
return qMin(scaleW, scaleH); |
|
} |
|
return 0; |
|
} |
|
|
|
void PageView::updateZoom( ZoomMode newZoomMode ) |
|
{ |
|
if ( newZoomMode == ZoomFixed ) |
|
{ |
|
if ( d->aZoom->currentItem() == 0 ) |
|
newZoomMode = ZoomFitWidth; |
|
else if ( d->aZoom->currentItem() == 1 ) |
|
newZoomMode = ZoomFitPage; |
|
else if ( d->aZoom->currentItem() == 2 ) |
|
newZoomMode = ZoomFitAuto; |
|
} |
|
|
|
float newFactor = d->zoomFactor; |
|
QAction * checkedZoomAction = 0; |
|
switch ( newZoomMode ) |
|
{ |
|
case ZoomFixed:{ //ZoomFixed case |
|
QString z = d->aZoom->currentText(); |
|
// kdelibs4 sometimes adds accelerators to actions' text directly :( |
|
z.remove ('&'); |
|
z.remove ('%'); |
|
newFactor = KGlobal::locale()->readNumber( z ) / 100.0; |
|
}break; |
|
case ZoomIn: |
|
case ZoomOut:{ |
|
const float zoomFactorFitWidth = zoomFactorFitMode(ZoomFitWidth); |
|
const float zoomFactorFitPage = zoomFactorFitMode(ZoomFitPage); |
|
QVector<float> zoomValue(15); |
|
qCopy(kZoomValues, kZoomValues + 13, zoomValue.begin()); |
|
zoomValue[13] = zoomFactorFitWidth; |
|
zoomValue[14] = zoomFactorFitPage; |
|
qSort(zoomValue.begin(), zoomValue.end()); |
|
QVector<float>::iterator i; |
|
if ( newZoomMode == ZoomOut ) |
|
{ |
|
if (newFactor <= zoomValue.first()) |
|
return; |
|
i = qLowerBound(zoomValue.begin(), zoomValue.end(), newFactor) - 1; |
|
} |
|
else |
|
{ |
|
if (newFactor >= zoomValue.last()) |
|
return; |
|
i = qUpperBound(zoomValue.begin(), zoomValue.end(), newFactor); |
|
} |
|
const float tmpFactor = *i; |
|
if ( tmpFactor == zoomFactorFitWidth ) |
|
{ |
|
newZoomMode = ZoomFitWidth; |
|
checkedZoomAction = d->aZoomFitWidth; |
|
} |
|
else if ( tmpFactor == zoomFactorFitPage ) |
|
{ |
|
newZoomMode = ZoomFitPage; |
|
checkedZoomAction = d->aZoomFitPage; |
|
} |
|
else |
|
{ |
|
newFactor = tmpFactor; |
|
newZoomMode = ZoomFixed; |
|
} |
|
} |
|
break; |
|
case ZoomFitWidth: |
|
checkedZoomAction = d->aZoomFitWidth; |
|
break; |
|
case ZoomFitPage: |
|
checkedZoomAction = d->aZoomFitPage; |
|
break; |
|
case ZoomFitAuto: |
|
checkedZoomAction = d->aZoomAutoFit; |
|
break; |
|
case ZoomRefreshCurrent: |
|
newZoomMode = ZoomFixed; |
|
d->zoomFactor = -1; |
|
break; |
|
} |
|
const float upperZoomLimit = d->document->supportsTiles() ? 16.0 : 4.0; |
|
if ( newFactor > upperZoomLimit ) |
|
newFactor = upperZoomLimit; |
|
if ( newFactor < 0.1 ) |
|
newFactor = 0.1; |
|
|
|
if ( newZoomMode != d->zoomMode || (newZoomMode == ZoomFixed && newFactor != d->zoomFactor ) ) |
|
{ |
|
// rebuild layout and update the whole viewport |
|
d->zoomMode = newZoomMode; |
|
d->zoomFactor = newFactor; |
|
// be sure to block updates to document's viewport |
|
bool prevState = d->blockViewport; |
|
d->blockViewport = true; |
|
slotRelayoutPages(); |
|
d->blockViewport = prevState; |
|
// request pixmaps |
|
slotRequestVisiblePixmaps(); |
|
// update zoom text |
|
updateZoomText(); |
|
// update actions checked state |
|
if ( d->aZoomFitWidth ) |
|
{ |
|
d->aZoomFitWidth->setChecked( checkedZoomAction == d->aZoomFitWidth ); |
|
d->aZoomFitPage->setChecked( checkedZoomAction == d->aZoomFitPage ); |
|
d->aZoomAutoFit->setChecked( checkedZoomAction == d->aZoomAutoFit ); |
|
} |
|
} |
|
else if ( newZoomMode == ZoomFixed && newFactor == d->zoomFactor ) |
|
updateZoomText(); |
|
|
|
d->aZoomIn->setEnabled( d->zoomFactor < upperZoomLimit-0.001 ); |
|
d->aZoomOut->setEnabled( d->zoomFactor > 0.101 ); |
|
} |
|
|
|
void PageView::updateZoomText() |
|
{ |
|
// use current page zoom as zoomFactor if in ZoomFit/* mode |
|
if ( d->zoomMode != ZoomFixed && d->items.count() > 0 ) |
|
d->zoomFactor = d->items[ qMax( 0, (int)d->document->currentPage() ) ]->zoomFactor(); |
|
float newFactor = d->zoomFactor; |
|
d->aZoom->removeAllActions(); |
|
|
|
// add items that describe fit actions |
|
QStringList translated; |
|
translated << i18n("Fit Width") << i18n("Fit Page") << i18n("Auto Fit"); |
|
|
|
// add percent items |
|
const QString single_oh( "0" ); |
|
int idx = 0, selIdx = 3; |
|
bool inserted = false; //use: "d->zoomMode != ZoomFixed" to hide Fit/* zoom ratio |
|
int zoomValueCount = 11; |
|
if ( d->document->supportsTiles() ) |
|
zoomValueCount = 13; |
|
while ( idx < zoomValueCount || !inserted ) |
|
{ |
|
float value = idx < zoomValueCount ? kZoomValues[ idx ] : newFactor; |
|
if ( !inserted && newFactor < (value - 0.0001) ) |
|
value = newFactor; |
|
else |
|
idx ++; |
|
if ( value > (newFactor - 0.0001) && value < (newFactor + 0.0001) ) |
|
inserted = true; |
|
if ( !inserted ) |
|
selIdx++; |
|
// we do not need to display 2-digit precision |
|
QString localValue( KGlobal::locale()->formatNumber( value * 100.0, 1 ) ); |
|
localValue.remove( KGlobal::locale()->decimalSymbol() + single_oh ); |
|
// remove a trailing zero in numbers like 66.70 |
|
if ( localValue.right( 1 ) == QLatin1String( "0" ) && localValue.indexOf( KGlobal::locale()->decimalSymbol() ) > -1 ) |
|
localValue.chop( 1 ); |
|
translated << QString( "%1%" ).arg( localValue ); |
|
} |
|
d->aZoom->setItems( translated ); |
|
|
|
// select current item in list |
|
if ( d->zoomMode == ZoomFitWidth ) |
|
selIdx = 0; |
|
else if ( d->zoomMode == ZoomFitPage ) |
|
selIdx = 1; |
|
else if ( d->zoomMode == ZoomFitAuto ) |
|
selIdx = 2; |
|
// we have to temporarily enable the actions as otherwise we can't set a new current item |
|
d->aZoom->setEnabled( true ); |
|
d->aZoom->selectableActionGroup()->setEnabled( true ); |
|
d->aZoom->setCurrentItem( selIdx ); |
|
d->aZoom->setEnabled( d->items.size() > 0 ); |
|
d->aZoom->selectableActionGroup()->setEnabled( d->items.size() > 0 ); |
|
} |
|
|
|
void PageView::updateCursor() |
|
{ |
|
const QPoint p = contentAreaPosition() + viewport()->mapFromGlobal( QCursor::pos() ); |
|
updateCursor( p ); |
|
} |
|
|
|
void PageView::updateCursor( const QPoint &p ) |
|
{ |
|
// detect the underlaying page (if present) |
|
PageViewItem * pageItem = pickItemOnPoint( p.x(), p.y() ); |
|
|
|
if ( d->annotator && d->annotator->active() ) |
|
{ |
|
if ( pageItem || d->annotator->annotating() ) |
|
setCursor( d->annotator->cursor() ); |
|
else |
|
setCursor( Qt::ForbiddenCursor ); |
|
} |
|
else if ( pageItem ) |
|
{ |
|
double nX = pageItem->absToPageX(p.x()); |
|
double nY = pageItem->absToPageY(p.y()); |
|
|
|
// if over a ObjectRect (of type Link) change cursor to hand |
|
if ( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TextSelect ) |
|
setCursor( Qt::IBeamCursor ); |
|
else if ( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Magnifier ) |
|
setCursor( Qt::CrossCursor ); |
|
else if ( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::RectSelect ) |
|
setCursor( Qt::CrossCursor ); |
|
else if ( d->mouseAnn ) |
|
setCursor( Qt::ClosedHandCursor ); |
|
else if ( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Browse ) |
|
{ |
|
const Okular::ObjectRect * linkobj = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
const Okular::ObjectRect * annotobj = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); |
|
if ( linkobj && !annotobj ) |
|
{ |
|
d->mouseOnRect = true; |
|
setCursor( Qt::PointingHandCursor ); |
|
} |
|
else |
|
{ |
|
d->mouseOnRect = false; |
|
if ( annotobj ) |
|
{ |
|
const Okular::Annotation *annotation = static_cast< const Okular::AnnotationObjectRect * >( annotobj )->annotation(); |
|
if ( ( QApplication::keyboardModifiers() & Qt::ControlModifier ) |
|
&& annotation->canBeMoved() ) |
|
{ |
|
setCursor( Qt::OpenHandCursor ); |
|
} |
|
else if ( annotation->subType() == Okular::Annotation::AMovie ) |
|
{ |
|
d->mouseOnRect = true; |
|
setCursor( Qt::PointingHandCursor ); |
|
} |
|
else if ( annotation->subType() == Okular::Annotation::AScreen ) |
|
{ |
|
if ( GuiUtils::renditionMovieFromScreenAnnotation( static_cast< const Okular::ScreenAnnotation * >( annotation ) ) != 0 ) |
|
{ |
|
d->mouseOnRect = true; |
|
setCursor( Qt::PointingHandCursor ); |
|
} |
|
} |
|
else |
|
{ |
|
setCursor( Qt::OpenHandCursor ); |
|
} |
|
} |
|
else |
|
{ |
|
setCursor( Qt::OpenHandCursor ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
setCursor( Qt::ArrowCursor ); |
|
} |
|
} |
|
else |
|
{ |
|
// if there's no page over the cursor and we were showing the pointingHandCursor |
|
// go back to the normal one |
|
d->mouseOnRect = false; |
|
setCursor( Qt::ArrowCursor ); |
|
} |
|
} |
|
|
|
void PageView::moveMagnifier( const QPoint& p ) // non scaled point |
|
{ |
|
const int w = d->magnifierView->width() * 0.5; |
|
const int h = d->magnifierView->height() * 0.5; |
|
|
|
int x = p.x() - w; |
|
int y = p.y() - h; |
|
|
|
const int max_x = viewport()->width(); |
|
const int max_y = viewport()->height(); |
|
|
|
QPoint scroll(0,0); |
|
|
|
if (x < 0) |
|
{ |
|
if (horizontalScrollBar()->value() > 0) scroll.setX(x - w); |
|
x = 0; |
|
} |
|
|
|
if (y < 0) |
|
{ |
|
if (verticalScrollBar()->value() > 0) scroll.setY(y - h); |
|
y = 0; |
|
} |
|
|
|
if (p.x() + w > max_x) |
|
{ |
|
if (horizontalScrollBar()->value() < horizontalScrollBar()->maximum()) scroll.setX(p.x() + 2 * w - max_x); |
|
x = max_x - d->magnifierView->width() - 1; |
|
} |
|
|
|
if (p.y() + h > max_y) |
|
{ |
|
if (verticalScrollBar()->value() < verticalScrollBar()->maximum()) scroll.setY(p.y() + 2 * h - max_y); |
|
y = max_y - d->magnifierView->height() - 1; |
|
} |
|
|
|
if (!scroll.isNull()) |
|
scrollPosIntoView(contentAreaPoint(p + scroll)); |
|
|
|
d->magnifierView->move(x, y); |
|
} |
|
|
|
void PageView::updateMagnifier( const QPoint& p ) // scaled point |
|
{ |
|
/* translate mouse coordinates to page coordinates and inform the magnifier of the situation */ |
|
PageViewItem *item = pickItemOnPoint(p.x(), p.y()); |
|
if (item) |
|
{ |
|
Okular::NormalizedPoint np(item->absToPageX(p.x()), item->absToPageY(p.y())); |
|
d->magnifierView->updateView( np, item->page() ); |
|
} |
|
} |
|
|
|
int PageView::viewColumns() const |
|
{ |
|
int vm = Okular::Settings::viewMode(); |
|
if (vm == Okular::Settings::EnumViewMode::Single) return 1; |
|
else if (vm == Okular::Settings::EnumViewMode::Facing || |
|
vm == Okular::Settings::EnumViewMode::FacingFirstCentered) return 2; |
|
else return Okular::Settings::viewColumns(); |
|
} |
|
|
|
void PageView::center(int cx, int cy) |
|
{ |
|
scrollTo( cx - viewport()->width() / 2, cy - viewport()->height() / 2 ); |
|
} |
|
|
|
void PageView::scrollTo( int x, int y ) |
|
{ |
|
bool prevState = d->blockPixmapsRequest; |
|
|
|
int newValue = -1; |
|
if ( x != horizontalScrollBar()->value() || y != verticalScrollBar()->value() ) |
|
newValue = 1; // Pretend this call is the result of a scrollbar event |
|
|
|
d->blockPixmapsRequest = true; |
|
horizontalScrollBar()->setValue( x ); |
|
verticalScrollBar()->setValue( y ); |
|
|
|
d->blockPixmapsRequest = prevState; |
|
|
|
slotRequestVisiblePixmaps( newValue ); |
|
} |
|
|
|
void PageView::toggleFormWidgets( bool on ) |
|
{ |
|
bool somehadfocus = false; |
|
QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); |
|
for ( ; dIt != dEnd; ++dIt ) |
|
{ |
|
bool hadfocus = (*dIt)->setFormWidgetsVisible( on ); |
|
somehadfocus = somehadfocus || hadfocus; |
|
} |
|
if ( somehadfocus ) |
|
setFocus(); |
|
d->m_formsVisible = on; |
|
if ( d->aToggleForms ) // it may not exist if we are on dummy mode |
|
{ |
|
if ( d->m_formsVisible ) |
|
{ |
|
d->aToggleForms->setText( i18n( "Hide Forms" ) ); |
|
} |
|
else |
|
{ |
|
d->aToggleForms->setText( i18n( "Show Forms" ) ); |
|
} |
|
} |
|
} |
|
|
|
void PageView::resizeContentArea( const QSize & newSize ) |
|
{ |
|
const QSize vs = viewport()->size(); |
|
horizontalScrollBar()->setRange( 0, newSize.width() - vs.width() ); |
|
verticalScrollBar()->setRange( 0, newSize.height() - vs.height() ); |
|
updatePageStep(); |
|
} |
|
|
|
void PageView::updatePageStep() { |
|
const QSize vs = viewport()->size(); |
|
horizontalScrollBar()->setPageStep( vs.width() ); |
|
verticalScrollBar()->setPageStep( vs.height() * (100 - Okular::Settings::scrollOverlap()) / 100 ); |
|
} |
|
|
|
void PageView::addWebShortcutsMenu( KMenu * menu, const QString & text ) |
|
{ |
|
#if KDE_IS_VERSION(4,5,70) |
|
if ( text.isEmpty() ) |
|
{ |
|
return; |
|
} |
|
|
|
QString searchText = text; |
|
searchText = searchText.replace( '\n', ' ' ).replace( '\r', ' ' ).simplified(); |
|
|
|
if ( searchText.isEmpty() ) |
|
{ |
|
return; |
|
} |
|
|
|
KUriFilterData filterData( searchText ); |
|
|
|
filterData.setSearchFilteringOptions( KUriFilterData::RetrievePreferredSearchProvidersOnly ); |
|
|
|
if ( KUriFilter::self()->filterSearchUri( filterData, KUriFilter::NormalTextFilter ) ) |
|
{ |
|
const QStringList searchProviders = filterData.preferredSearchProviders(); |
|
|
|
if ( !searchProviders.isEmpty() ) |
|
{ |
|
KMenu *webShortcutsMenu = new KMenu( menu ); |
|
webShortcutsMenu->setIcon( KIcon( "preferences-web-browser-shortcuts" ) ); |
|
|
|
const QString squeezedText = KStringHandler::rsqueeze( searchText, 21 ); |
|
webShortcutsMenu->setTitle( i18n( "Search for '%1' with", squeezedText ) ); |
|
|
|
KAction *action = 0; |
|
|
|
foreach( const QString &searchProvider, searchProviders ) |
|
{ |
|
action = new KAction( searchProvider, webShortcutsMenu ); |
|
action->setIcon( KIcon( filterData.iconNameForPreferredSearchProvider( searchProvider ) ) ); |
|
action->setData( filterData.queryForPreferredSearchProvider( searchProvider ) ); |
|
connect( action, SIGNAL(triggered()), this, SLOT(slotHandleWebShortcutAction()) ); |
|
webShortcutsMenu->addAction( action ); |
|
} |
|
|
|
webShortcutsMenu->addSeparator(); |
|
|
|
action = new KAction( i18n( "Configure Web Shortcuts..." ), webShortcutsMenu ); |
|
action->setIcon( KIcon( "configure" ) ); |
|
connect( action, SIGNAL(triggered()), this, SLOT(slotConfigureWebShortcuts()) ); |
|
webShortcutsMenu->addAction( action ); |
|
|
|
menu->addMenu(webShortcutsMenu); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
//BEGIN private SLOTS |
|
void PageView::slotRelayoutPages() |
|
// called by: notifySetup, viewportResizeEvent, slotViewMode, slotContinuousToggled, updateZoom |
|
{ |
|
// set an empty container if we have no pages |
|
const int pageCount = d->items.count(); |
|
if ( pageCount < 1 ) |
|
{ |
|
return; |
|
} |
|
|
|
// if viewport was auto-moving, stop it |
|
if ( d->viewportMoveActive ) |
|
{ |
|
center( d->viewportMoveDest.x(), d->viewportMoveDest.y() ); |
|
d->viewportMoveActive = false; |
|
d->viewportMoveTimer->stop(); |
|
verticalScrollBar()->setEnabled( true ); |
|
horizontalScrollBar()->setEnabled( true ); |
|
} |
|
|
|
// common iterator used in this method and viewport parameters |
|
QVector< PageViewItem * >::const_iterator iIt, iEnd = d->items.constEnd(); |
|
int viewportWidth = viewport()->width(), |
|
viewportHeight = viewport()->height(), |
|
fullWidth = 0, |
|
fullHeight = 0; |
|
QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewportWidth, viewportHeight ); |
|
|
|
// handle the 'center first page in row' stuff |
|
const bool facing = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount > 1; |
|
const bool facingCentered = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered || |
|
(Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount == 1); |
|
const bool overrideCentering = facingCentered && pageCount < 3; |
|
const bool centerFirstPage = facingCentered && !overrideCentering; |
|
const bool facingPages = facing || centerFirstPage; |
|
const bool centerLastPage = centerFirstPage && pageCount % 2 == 0; |
|
const bool continuousView = Okular::Settings::viewContinuous(); |
|
const int nCols = overrideCentering ? 1 : viewColumns(); |
|
|
|
// set all items geometry and resize contents. handle 'continuous' and 'single' modes separately |
|
|
|
PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage() ) ]; |
|
|
|
// Here we find out column's width and row's height to compute a table |
|
// so we can place widgets 'centered in virtual cells'. |
|
const int nRows = (int)ceil( (float)(centerFirstPage ? (pageCount + nCols - 1) : pageCount) / (float)nCols ); |
|
|
|
int * colWidth = new int[ nCols ], |
|
* rowHeight = new int[ nRows ], |
|
cIdx = 0, |
|
rIdx = 0; |
|
for ( int i = 0; i < nCols; i++ ) |
|
colWidth[ i ] = viewportWidth / nCols; |
|
for ( int i = 0; i < nRows; i++ ) |
|
rowHeight[ i ] = 0; |
|
// handle the 'centering on first row' stuff |
|
if ( centerFirstPage ) |
|
cIdx += nCols - 1; |
|
|
|
// 1) find the maximum columns width and rows height for a grid in |
|
// which each page must well-fit inside a cell |
|
for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt ) |
|
{ |
|
PageViewItem * item = *iIt; |
|
// update internal page size (leaving a little margin in case of Fit* modes) |
|
updateItemSize( item, colWidth[ cIdx ] - kcolWidthMargin, viewportHeight - krowHeightMargin ); |
|
// find row's maximum height and column's max width |
|
if ( item->croppedWidth() + kcolWidthMargin > colWidth[ cIdx ] ) |
|
colWidth[ cIdx ] = item->croppedWidth() + kcolWidthMargin; |
|
if ( item->croppedHeight() + krowHeightMargin > rowHeight[ rIdx ] ) |
|
rowHeight[ rIdx ] = item->croppedHeight() + krowHeightMargin; |
|
// handle the 'centering on first row' stuff |
|
// update col/row indices |
|
if ( ++cIdx == nCols ) |
|
{ |
|
cIdx = 0; |
|
rIdx++; |
|
} |
|
} |
|
|
|
const int pageRowIdx = ( ( centerFirstPage ? nCols - 1 : 0 ) + currentItem->pageNumber() ) / nCols; |
|
|
|
// 2) compute full size |
|
for ( int i = 0; i < nCols; i++ ) |
|
fullWidth += colWidth[ i ]; |
|
if ( continuousView ) |
|
{ |
|
for ( int i = 0; i < nRows; i++ ) |
|
fullHeight += rowHeight[ i ]; |
|
} |
|
else |
|
fullHeight = rowHeight[ pageRowIdx ]; |
|
|
|
// 3) arrange widgets inside cells (and refine fullHeight if needed) |
|
int insertX = 0, |
|
insertY = fullHeight < viewportHeight ? ( viewportHeight - fullHeight ) / 2 : 0; |
|
const int origInsertY = insertY; |
|
cIdx = 0; |
|
rIdx = 0; |
|
if ( centerFirstPage ) |
|
{ |
|
cIdx += nCols - 1; |
|
for ( int i = 0; i < cIdx; ++i ) |
|
insertX += colWidth[ i ]; |
|
} |
|
for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt ) |
|
{ |
|
PageViewItem * item = *iIt; |
|
int cWidth = colWidth[ cIdx ], |
|
rHeight = rowHeight[ rIdx ]; |
|
if ( continuousView || rIdx == pageRowIdx ) |
|
{ |
|
const bool reallyDoCenterFirst = item->pageNumber() == 0 && centerFirstPage; |
|
const bool reallyDoCenterLast = item->pageNumber() == pageCount - 1 && centerLastPage; |
|
int actualX = 0; |
|
if ( reallyDoCenterFirst || reallyDoCenterLast ) |
|
{ |
|
// page is centered across entire viewport |
|
actualX = (fullWidth - item->croppedWidth()) / 2; |
|
} |
|
else if ( facingPages ) |
|
{ |
|
// page edges 'touch' the center of the viewport |
|
actualX = ( (centerFirstPage && item->pageNumber() % 2 == 1) || |
|
(!centerFirstPage && item->pageNumber() % 2 == 0) ) ? |
|
(fullWidth / 2) - item->croppedWidth() - 1 : (fullWidth / 2) + 1; |
|
} |
|
else |
|
{ |
|
// page is centered within its virtual column |
|
actualX = insertX + (cWidth - item->croppedWidth()) / 2; |
|
} |
|
item->moveTo( actualX, |
|
(continuousView ? insertY : origInsertY) + (rHeight - item->croppedHeight()) / 2 ); |
|
item->setVisible( true ); |
|
} |
|
else |
|
{ |
|
item->moveTo( 0, 0 ); |
|
item->setVisible( false ); |
|
} |
|
item->setFormWidgetsVisible( d->m_formsVisible ); |
|
// advance col/row index |
|
insertX += cWidth; |
|
if ( ++cIdx == nCols ) |
|
{ |
|
cIdx = 0; |
|
rIdx++; |
|
insertX = 0; |
|
insertY += rHeight; |
|
} |
|
#ifdef PAGEVIEW_DEBUG |
|
kWarning() << "updating size for pageno" << item->pageNumber() << "cropped" << item->croppedGeometry() << "uncropped" << item->uncroppedGeometry(); |
|
#endif |
|
} |
|
|
|
delete [] colWidth; |
|
delete [] rowHeight; |
|
|
|
// 3) reset dirty state |
|
d->dirtyLayout = false; |
|
|
|
// 4) update scrollview's contents size and recenter view |
|
bool wasUpdatesEnabled = viewport()->updatesEnabled(); |
|
if ( fullWidth != contentAreaWidth() || fullHeight != contentAreaHeight() ) |
|
{ |
|
const Okular::DocumentViewport vp = d->document->viewport(); |
|
// disable updates and resize the viewportContents |
|
if ( wasUpdatesEnabled ) |
|
viewport()->setUpdatesEnabled( false ); |
|
resizeContentArea( QSize( fullWidth, fullHeight ) ); |
|
// restore previous viewport if defined and updates enabled |
|
if ( wasUpdatesEnabled ) |
|
{ |
|
if ( vp.pageNumber >= 0 ) |
|
{ |
|
int prevX = horizontalScrollBar()->value(), |
|
prevY = verticalScrollBar()->value(); |
|
const QRect & geometry = d->items[ vp.pageNumber ]->croppedGeometry(); |
|
double nX = vp.rePos.enabled ? normClamp( vp.rePos.normalizedX, 0.5 ) : 0.5, |
|
nY = vp.rePos.enabled ? normClamp( vp.rePos.normalizedY, 0.0 ) : 0.0; |
|
center( geometry.left() + qRound( nX * (double)geometry.width() ), |
|
geometry.top() + qRound( nY * (double)geometry.height() ) ); |
|
// center() usually moves the viewport, that requests pixmaps too. |
|
// if that doesn't happen we have to request them by hand |
|
if ( prevX == horizontalScrollBar()->value() && prevY == verticalScrollBar()->value() ) |
|
slotRequestVisiblePixmaps(); |
|
} |
|
// or else go to center page |
|
else |
|
center( fullWidth / 2, 0 ); |
|
viewport()->setUpdatesEnabled( true ); |
|
} |
|
} |
|
|
|
// 5) update the whole viewport if updated enabled |
|
if ( wasUpdatesEnabled ) |
|
viewport()->update(); |
|
} |
|
|
|
void PageView::delayedResizeEvent() |
|
{ |
|
// If we already got here we don't need to execute the timer slot again |
|
d->delayResizeEventTimer->stop(); |
|
slotRelayoutPages(); |
|
slotRequestVisiblePixmaps(); |
|
} |
|
|
|
static void slotRequestPreloadPixmap( Okular::DocumentObserver * observer, const PageViewItem * i, const QRect &expandedViewportRect, QLinkedList< Okular::PixmapRequest * > *requestedPixmaps ) |
|
{ |
|
Okular::NormalizedRect preRenderRegion; |
|
const QRect intersectionRect = expandedViewportRect.intersect( i->croppedGeometry() ); |
|
if ( !intersectionRect.isEmpty() ) |
|
preRenderRegion = Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ); |
|
|
|
// request the pixmap if not already present |
|
if ( !i->page()->hasPixmap( observer, i->uncroppedWidth(), i->uncroppedHeight(), preRenderRegion ) && i->uncroppedWidth() > 0 ) |
|
{ |
|
Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload; |
|
requestFeatures |= Okular::PixmapRequest::Asynchronous; |
|
const bool pageHasTilesManager = i->page()->hasTilesManager( observer ); |
|
if ( pageHasTilesManager && !preRenderRegion.isNull() ) |
|
{ |
|
Okular::PixmapRequest * p = new Okular::PixmapRequest( observer, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, requestFeatures ); |
|
requestedPixmaps->push_back( p ); |
|
|
|
p->setNormalizedRect( preRenderRegion ); |
|
p->setTile( true ); |
|
} |
|
else if ( !pageHasTilesManager ) |
|
{ |
|
Okular::PixmapRequest * p = new Okular::PixmapRequest( observer, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, requestFeatures ); |
|
requestedPixmaps->push_back( p ); |
|
p->setNormalizedRect( preRenderRegion ); |
|
} |
|
} |
|
} |
|
|
|
void PageView::slotRequestVisiblePixmaps( int newValue ) |
|
{ |
|
// if requests are blocked (because raised by an unwanted event), exit |
|
if ( d->blockPixmapsRequest || d->viewportMoveActive || |
|
( QApplication::mouseButtons () & Qt::MidButton ) ) |
|
return; |
|
|
|
// precalc view limits for intersecting with page coords inside the loop |
|
const bool isEvent = newValue != -1 && !d->blockViewport; |
|
const QRect viewportRect( horizontalScrollBar()->value(), |
|
verticalScrollBar()->value(), |
|
viewport()->width(), viewport()->height() ); |
|
const QRect viewportRectAtZeroZero( 0, 0, viewport()->width(), viewport()->height() ); |
|
|
|
// some variables used to determine the viewport |
|
int nearPageNumber = -1; |
|
const double viewportCenterX = (viewportRect.left() + viewportRect.right()) / 2.0; |
|
const double viewportCenterY = (viewportRect.top() + viewportRect.bottom()) / 2.0; |
|
double focusedX = 0.5, |
|
focusedY = 0.0, |
|
minDistance = -1.0; |
|
// Margin (in pixels) around the viewport to preload |
|
const int pixelsToExpand = 512; |
|
|
|
// iterate over all items |
|
d->visibleItems.clear(); |
|
QLinkedList< Okular::PixmapRequest * > requestedPixmaps; |
|
QVector< Okular::VisiblePageRect * > visibleRects; |
|
QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); |
|
for ( ; iIt != iEnd; ++iIt ) |
|
{ |
|
PageViewItem * i = *iIt; |
|
foreach( FormWidgetIface *fwi, i->formWidgets() ) |
|
{ |
|
Okular::NormalizedRect r = fwi->rect(); |
|
fwi->moveTo( |
|
qRound( i->uncroppedGeometry().left() + i->uncroppedWidth() * r.left ) + 1 - viewportRect.left(), |
|
qRound( i->uncroppedGeometry().top() + i->uncroppedHeight() * r.top ) + 1 - viewportRect.top() ); |
|
} |
|
Q_FOREACH ( VideoWidget *vw, i->videoWidgets() ) |
|
{ |
|
const Okular::NormalizedRect r = vw->normGeometry(); |
|
vw->move( |
|
qRound( i->uncroppedGeometry().left() + i->uncroppedWidth() * r.left ) + 1 - viewportRect.left(), |
|
qRound( i->uncroppedGeometry().top() + i->uncroppedHeight() * r.top ) + 1 - viewportRect.top() ); |
|
|
|
if ( vw->isPlaying() && viewportRectAtZeroZero.intersect( vw->geometry() ).isEmpty() ) { |
|
vw->stop(); |
|
vw->pageLeft(); |
|
} |
|
} |
|
|
|
if ( !i->isVisible() ) |
|
continue; |
|
#ifdef PAGEVIEW_DEBUG |
|
kWarning() << "checking page" << i->pageNumber(); |
|
kWarning().nospace() << "viewportRect is " << viewportRect << ", page item is " << i->croppedGeometry() << " intersect : " << viewportRect.intersects( i->croppedGeometry() ); |
|
#endif |
|
// if the item doesn't intersect the viewport, skip it |
|
QRect intersectionRect = viewportRect.intersect( i->croppedGeometry() ); |
|
if ( intersectionRect.isEmpty() ) |
|
{ |
|
continue; |
|
} |
|
|
|
// add the item to the 'visible list' |
|
d->visibleItems.push_back( i ); |
|
Okular::VisiblePageRect * vItem = new Okular::VisiblePageRect( i->pageNumber(), Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ) ); |
|
visibleRects.push_back( vItem ); |
|
#ifdef PAGEVIEW_DEBUG |
|
kWarning() << "checking for pixmap for page" << i->pageNumber() << "=" << i->page()->hasPixmap( this, i->uncroppedWidth(), i->uncroppedHeight() ); |
|
kWarning() << "checking for text for page" << i->pageNumber() << "=" << i->page()->hasTextPage(); |
|
#endif |
|
|
|
Okular::NormalizedRect expandedVisibleRect = vItem->rect; |
|
if ( i->page()->hasTilesManager( this ) && Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Low ) |
|
{ |
|
double rectMargin = pixelsToExpand/(double)i->uncroppedHeight(); |
|
expandedVisibleRect.left = qMax( 0.0, vItem->rect.left - rectMargin ); |
|
expandedVisibleRect.top = qMax( 0.0, vItem->rect.top - rectMargin ); |
|
expandedVisibleRect.right = qMin( 1.0, vItem->rect.right + rectMargin ); |
|
expandedVisibleRect.bottom = qMin( 1.0, vItem->rect.bottom + rectMargin ); |
|
} |
|
|
|
// if the item has not the right pixmap, add a request for it |
|
if ( !i->page()->hasPixmap( this, i->uncroppedWidth(), i->uncroppedHeight(), expandedVisibleRect ) ) |
|
{ |
|
#ifdef PAGEVIEW_DEBUG |
|
kWarning() << "rerequesting visible pixmaps for page" << i->pageNumber() << "!"; |
|
#endif |
|
Okular::PixmapRequest * p = new Okular::PixmapRequest( this, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRIO, Okular::PixmapRequest::Asynchronous ); |
|
requestedPixmaps.push_back( p ); |
|
|
|
if ( i->page()->hasTilesManager( this ) ) |
|
{ |
|
p->setNormalizedRect( expandedVisibleRect ); |
|
p->setTile( true ); |
|
} |
|
else |
|
p->setNormalizedRect( vItem->rect ); |
|
} |
|
|
|
// look for the item closest to viewport center and the relative |
|
// position between the item and the viewport center |
|
if ( isEvent ) |
|
{ |
|
const QRect & geometry = i->croppedGeometry(); |
|
// compute distance between item center and viewport center (slightly moved left) |
|
double distance = hypot( (geometry.left() + geometry.right()) / 2 - (viewportCenterX - 4), |
|
(geometry.top() + geometry.bottom()) / 2 - viewportCenterY ); |
|
if ( distance >= minDistance && nearPageNumber != -1 ) |
|
continue; |
|
nearPageNumber = i->pageNumber(); |
|
minDistance = distance; |
|
if ( geometry.height() > 0 && geometry.width() > 0 ) |
|
{ |
|
focusedX = ( viewportCenterX - (double)geometry.left() ) / (double)geometry.width(); |
|
focusedY = ( viewportCenterY - (double)geometry.top() ) / (double)geometry.height(); |
|
} |
|
} |
|
} |
|
|
|
// if preloading is enabled, add the pages before and after in preloading |
|
if ( !d->visibleItems.isEmpty() && |
|
Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low ) |
|
{ |
|
// as the requests are done in the order as they appear in the list, |
|
// request first the next page and then the previous |
|
|
|
int pagesToPreload = viewColumns(); |
|
|
|
// if the greedy option is set, preload all pages |
|
if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy) |
|
pagesToPreload = d->items.count(); |
|
|
|
const QRect expandedViewportRect = viewportRect.adjusted( 0, -pixelsToExpand, 0, pixelsToExpand ); |
|
|
|
for( int j = 1; j <= pagesToPreload; j++ ) |
|
{ |
|
// add the page after the 'visible series' in preload |
|
const int tailRequest = d->visibleItems.last()->pageNumber() + j; |
|
if ( tailRequest < (int)d->items.count() ) |
|
{ |
|
slotRequestPreloadPixmap( this, d->items[ tailRequest ], expandedViewportRect, &requestedPixmaps ); |
|
} |
|
|
|
// add the page before the 'visible series' in preload |
|
const int headRequest = d->visibleItems.first()->pageNumber() - j; |
|
if ( headRequest >= 0 ) |
|
{ |
|
slotRequestPreloadPixmap( this, d->items[ headRequest ], expandedViewportRect, &requestedPixmaps ); |
|
} |
|
|
|
// stop if we've already reached both ends of the document |
|
if ( headRequest < 0 && tailRequest >= (int)d->items.count() ) |
|
break; |
|
} |
|
} |
|
|
|
// send requests to the document |
|
if ( !requestedPixmaps.isEmpty() ) |
|
{ |
|
d->document->requestPixmaps( requestedPixmaps ); |
|
} |
|
// if this functions was invoked by viewport events, send update to document |
|
if ( isEvent && nearPageNumber != -1 ) |
|
{ |
|
// determine the document viewport |
|
Okular::DocumentViewport newViewport( nearPageNumber ); |
|
newViewport.rePos.enabled = true; |
|
newViewport.rePos.normalizedX = focusedX; |
|
newViewport.rePos.normalizedY = focusedY; |
|
// set the viewport to other observers |
|
d->document->setViewport( newViewport , this ); |
|
} |
|
d->document->setVisiblePageRects( visibleRects, this ); |
|
} |
|
|
|
void PageView::slotMoveViewport() |
|
{ |
|
// converge to viewportMoveDest in 1 second |
|
int diffTime = d->viewportMoveTime.elapsed(); |
|
if ( diffTime >= 667 || !d->viewportMoveActive ) |
|
{ |
|
center( d->viewportMoveDest.x(), d->viewportMoveDest.y() ); |
|
d->viewportMoveTimer->stop(); |
|
d->viewportMoveActive = false; |
|
slotRequestVisiblePixmaps(); |
|
verticalScrollBar()->setEnabled( true ); |
|
horizontalScrollBar()->setEnabled( true ); |
|
return; |
|
} |
|
|
|
// move the viewport smoothly (kmplot: p(x)=1+0.47*(x-1)^3-0.25*(x-1)^4) |
|
float convergeSpeed = (float)diffTime / 667.0, |
|
x = ((float)viewport()->width() / 2.0) + horizontalScrollBar()->value(), |
|
y = ((float)viewport()->height() / 2.0) + verticalScrollBar()->value(), |
|
diffX = (float)d->viewportMoveDest.x() - x, |
|
diffY = (float)d->viewportMoveDest.y() - y; |
|
convergeSpeed *= convergeSpeed * (1.4 - convergeSpeed); |
|
center( (int)(x + diffX * convergeSpeed), |
|
(int)(y + diffY * convergeSpeed ) ); |
|
} |
|
|
|
void PageView::slotAutoScoll() |
|
{ |
|
// the first time create the timer |
|
if ( !d->autoScrollTimer ) |
|
{ |
|
d->autoScrollTimer = new QTimer( this ); |
|
d->autoScrollTimer->setSingleShot( true ); |
|
connect( d->autoScrollTimer, SIGNAL(timeout()), this, SLOT(slotAutoScoll()) ); |
|
} |
|
|
|
// if scrollIncrement is zero, stop the timer |
|
if ( !d->scrollIncrement ) |
|
{ |
|
d->autoScrollTimer->stop(); |
|
return; |
|
} |
|
|
|
// compute delay between timer ticks and scroll amount per tick |
|
int index = abs( d->scrollIncrement ) - 1; // 0..9 |
|
const int scrollDelay[10] = { 200, 100, 50, 30, 20, 30, 25, 20, 30, 20 }; |
|
const int scrollOffset[10] = { 1, 1, 1, 1, 1, 2, 2, 2, 4, 4 }; |
|
d->autoScrollTimer->start( scrollDelay[ index ] ); |
|
int delta = d->scrollIncrement > 0 ? scrollOffset[ index ] : -scrollOffset[ index ]; |
|
verticalScrollBar()->setValue(verticalScrollBar()->value() + delta); |
|
} |
|
|
|
void PageView::slotDragScroll() |
|
{ |
|
scrollTo( horizontalScrollBar()->value() + d->dragScrollVector.x(), verticalScrollBar()->value() + d->dragScrollVector.y() ); |
|
QPoint p = contentAreaPosition() + viewport()->mapFromGlobal( QCursor::pos() ); |
|
updateSelection( p ); |
|
} |
|
|
|
void PageView::slotShowWelcome() |
|
{ |
|
// show initial welcome text |
|
d->messageWindow->display( i18n( "Welcome" ), QString(), PageViewMessage::Info, 2000 ); |
|
} |
|
|
|
void PageView::slotShowSizeAllCursor() |
|
{ |
|
setCursor( Qt::SizeAllCursor ); |
|
} |
|
|
|
void PageView::slotHandleWebShortcutAction() |
|
{ |
|
#if KDE_IS_VERSION(4,5,70) |
|
KAction *action = qobject_cast<KAction*>( sender() ); |
|
|
|
if (action) |
|
{ |
|
KUriFilterData filterData( action->data().toString() ); |
|
|
|
if ( KUriFilter::self()->filterSearchUri( filterData, KUriFilter::WebShortcutFilter ) ) |
|
{ |
|
KToolInvocation::invokeBrowser( filterData.uri().url() ); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
void PageView::slotConfigureWebShortcuts() |
|
{ |
|
KToolInvocation::kdeinitExec( "kcmshell4", QStringList() << "ebrowsing" ); |
|
} |
|
|
|
void PageView::slotZoom() |
|
{ |
|
if ( !d->aZoom->selectableActionGroup()->isEnabled() ) |
|
return; |
|
|
|
setFocus(); |
|
updateZoom( ZoomFixed ); |
|
} |
|
|
|
void PageView::slotZoomIn() |
|
{ |
|
updateZoom( ZoomIn ); |
|
} |
|
|
|
void PageView::slotZoomOut() |
|
{ |
|
updateZoom( ZoomOut ); |
|
} |
|
|
|
void PageView::slotFitToWidthToggled( bool on ) |
|
{ |
|
if ( on ) updateZoom( ZoomFitWidth ); |
|
} |
|
|
|
void PageView::slotFitToPageToggled( bool on ) |
|
{ |
|
if ( on ) updateZoom( ZoomFitPage ); |
|
} |
|
|
|
void PageView::slotAutoFitToggled( bool on ) |
|
{ |
|
if ( on ) updateZoom( ZoomFitAuto ); |
|
} |
|
|
|
void PageView::slotViewMode( QAction *action ) |
|
{ |
|
const int nr = action->data().toInt(); |
|
if ( (int)Okular::Settings::viewMode() != nr ) |
|
{ |
|
Okular::Settings::setViewMode( nr ); |
|
Okular::Settings::self()->writeConfig(); |
|
if ( d->document->pages() > 0 ) |
|
slotRelayoutPages(); |
|
} |
|
} |
|
|
|
void PageView::slotContinuousToggled( bool on ) |
|
{ |
|
if ( Okular::Settings::viewContinuous() != on ) |
|
{ |
|
Okular::Settings::setViewContinuous( on ); |
|
Okular::Settings::self()->writeConfig(); |
|
if ( d->document->pages() > 0 ) |
|
slotRelayoutPages(); |
|
} |
|
} |
|
|
|
void PageView::slotSetMouseNormal() |
|
{ |
|
Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::Browse ); |
|
// hide the messageWindow |
|
d->messageWindow->hide(); |
|
// reshow the annotator toolbar if hiding was forced (and if it is not already visible) |
|
if ( d->annotator && d->annotator->hidingWasForced() && d->aToggleAnnotator && !d->aToggleAnnotator->isChecked() ) |
|
d->aToggleAnnotator->trigger(); |
|
// force an update of the cursor |
|
updateCursor(); |
|
Okular::Settings::self()->writeConfig(); |
|
} |
|
|
|
void PageView::slotSetMouseZoom() |
|
{ |
|
Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::Zoom ); |
|
// change the text in messageWindow (and show it if hidden) |
|
d->messageWindow->display( i18n( "Select zooming area. Right-click to zoom out." ), QString(), PageViewMessage::Info, -1 ); |
|
// force hiding of annotator toolbar |
|
if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) |
|
{ |
|
d->aToggleAnnotator->trigger(); |
|
d->annotator->setHidingForced( true ); |
|
} |
|
// force an update of the cursor |
|
updateCursor(); |
|
Okular::Settings::self()->writeConfig(); |
|
} |
|
|
|
void PageView::slotSetMouseMagnifier() |
|
{ |
|
Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::Magnifier ); |
|
d->messageWindow->display( i18n( "Click to see the magnified view." ), QString() ); |
|
|
|
// force an update of the cursor |
|
updateCursor(); |
|
Okular::Settings::self()->writeConfig(); |
|
} |
|
|
|
void PageView::slotSetMouseSelect() |
|
{ |
|
Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::RectSelect ); |
|
// change the text in messageWindow (and show it if hidden) |
|
d->messageWindow->display( i18n( "Draw a rectangle around the text/graphics to copy." ), QString(), PageViewMessage::Info, -1 ); |
|
// force hiding of annotator toolbar |
|
if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) |
|
{ |
|
d->aToggleAnnotator->trigger(); |
|
d->annotator->setHidingForced( true ); |
|
} |
|
// force an update of the cursor |
|
updateCursor(); |
|
Okular::Settings::self()->writeConfig(); |
|
} |
|
|
|
void PageView::slotSetMouseTextSelect() |
|
{ |
|
Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::TextSelect ); |
|
// change the text in messageWindow (and show it if hidden) |
|
d->messageWindow->display( i18n( "Select text" ), QString(), PageViewMessage::Info, -1 ); |
|
// force hiding of annotator toolbar |
|
if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) |
|
{ |
|
d->aToggleAnnotator->trigger(); |
|
d->annotator->setHidingForced( true ); |
|
} |
|
// force an update of the cursor |
|
updateCursor(); |
|
Okular::Settings::self()->writeConfig(); |
|
} |
|
|
|
void PageView::slotSetMouseTableSelect() |
|
{ |
|
Okular::Settings::setMouseMode( Okular::Settings::EnumMouseMode::TableSelect ); |
|
// change the text in messageWindow (and show it if hidden) |
|
d->messageWindow->display( i18n( |
|
"Draw a rectangle around the table, then click near edges to divide up; press Esc to clear." |
|
), QString(), PageViewMessage::Info, -1 ); |
|
// force hiding of annotator toolbar |
|
if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) |
|
{ |
|
d->aToggleAnnotator->trigger(); |
|
d->annotator->setHidingForced( true ); |
|
} |
|
// force an update of the cursor |
|
updateCursor(); |
|
Okular::Settings::self()->writeConfig(); |
|
} |
|
|
|
void PageView::slotToggleAnnotator( bool on ) |
|
{ |
|
// the 'inHere' trick is needed as the slotSetMouseZoom() calls this |
|
static bool inHere = false; |
|
if ( inHere ) |
|
return; |
|
inHere = true; |
|
|
|
// the annotator can be used in normal mouse mode only, so if asked for it, |
|
// switch to normal mode |
|
if ( on && Okular::Settings::mouseMode() != Okular::Settings::EnumMouseMode::Browse ) |
|
d->aMouseNormal->trigger(); |
|
|
|
// ask for Author's name if not already set |
|
if ( Okular::Settings::identityAuthor().isEmpty() ) |
|
{ |
|
// get default username from the kdelibs/kdecore/KUser |
|
KUser currentUser; |
|
QString userName = currentUser.property( KUser::FullName ).toString(); |
|
// ask the user for confirmation/change |
|
if ( userName.isEmpty() ) |
|
{ |
|
bool ok = false; |
|
userName = KInputDialog::getText( |
|
i18n( "Annotations author" ), |
|
i18n( "Please insert your name or initials:" ), |
|
QString(), |
|
&ok ); |
|
if ( !ok ) |
|
{ |
|
d->aToggleAnnotator->trigger(); |
|
inHere = false; |
|
return; |
|
} |
|
} |
|
// save the name |
|
Okular::Settings::setIdentityAuthor( userName ); |
|
Okular::Settings::self()->writeConfig(); |
|
} |
|
|
|
// create the annotator object if not present |
|
if ( !d->annotator ) |
|
{ |
|
d->annotator = new PageViewAnnotator( this, d->document ); |
|
bool allowTools = d->document->pages() > 0 && d->document->isAllowed( Okular::AllowNotes ); |
|
d->annotator->setToolsEnabled( allowTools ); |
|
d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() ); |
|
} |
|
|
|
// initialize/reset annotator (and show/hide toolbar) |
|
d->annotator->setEnabled( on ); |
|
d->annotator->setHidingForced( false ); |
|
|
|
inHere = false; |
|
} |
|
|
|
void PageView::slotAutoScrollUp() |
|
{ |
|
if ( d->scrollIncrement < -9 ) |
|
return; |
|
d->scrollIncrement--; |
|
slotAutoScoll(); |
|
setFocus(); |
|
} |
|
|
|
void PageView::slotAutoScrollDown() |
|
{ |
|
if ( d->scrollIncrement > 9 ) |
|
return; |
|
d->scrollIncrement++; |
|
slotAutoScoll(); |
|
setFocus(); |
|
} |
|
|
|
void PageView::slotScrollUp( bool singleStep ) |
|
{ |
|
// if in single page mode and at the top of the screen, go to \ page |
|
if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() > verticalScrollBar()->minimum() ) |
|
{ |
|
if ( singleStep ) |
|
verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub ); |
|
else |
|
verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepSub ); |
|
} |
|
else if ( d->document->currentPage() > 0 ) |
|
{ |
|
// more optimized than document->setPrevPage and then move view to bottom |
|
Okular::DocumentViewport newViewport = d->document->viewport(); |
|
newViewport.pageNumber -= viewColumns(); |
|
if ( newViewport.pageNumber < 0 ) |
|
newViewport.pageNumber = 0; |
|
newViewport.rePos.enabled = true; |
|
newViewport.rePos.normalizedY = 1.0; |
|
d->document->setViewport( newViewport ); |
|
} |
|
} |
|
|
|
void PageView::slotScrollDown( bool singleStep ) |
|
{ |
|
// if in single page mode and at the bottom of the screen, go to next page |
|
if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() < verticalScrollBar()->maximum() ) |
|
{ |
|
if ( singleStep ) |
|
verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd ); |
|
else |
|
verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepAdd ); |
|
} |
|
else if ( (int)d->document->currentPage() < d->items.count() - 1 ) |
|
{ |
|
// more optimized than document->setNextPage and then move view to top |
|
Okular::DocumentViewport newViewport = d->document->viewport(); |
|
newViewport.pageNumber += viewColumns(); |
|
if ( newViewport.pageNumber >= (int)d->items.count() ) |
|
newViewport.pageNumber = d->items.count() - 1; |
|
newViewport.rePos.enabled = true; |
|
newViewport.rePos.normalizedY = 0.0; |
|
d->document->setViewport( newViewport ); |
|
} |
|
} |
|
|
|
void PageView::slotRotateClockwise() |
|
{ |
|
int id = ( (int)d->document->rotation() + 1 ) % 4; |
|
d->document->setRotation( id ); |
|
} |
|
|
|
void PageView::slotRotateCounterClockwise() |
|
{ |
|
int id = ( (int)d->document->rotation() + 3 ) % 4; |
|
d->document->setRotation( id ); |
|
} |
|
|
|
void PageView::slotRotateOriginal() |
|
{ |
|
d->document->setRotation( 0 ); |
|
} |
|
|
|
void PageView::slotPageSizes( int newsize ) |
|
{ |
|
if ( newsize < 0 || newsize >= d->document->pageSizes().count() ) |
|
return; |
|
|
|
d->document->setPageSize( d->document->pageSizes().at( newsize ) ); |
|
} |
|
|
|
void PageView::slotTrimMarginsToggled( bool on ) |
|
{ |
|
if ( Okular::Settings::trimMargins() != on ) |
|
{ |
|
Okular::Settings::setTrimMargins( on ); |
|
Okular::Settings::self()->writeConfig(); |
|
if ( d->document->pages() > 0 ) |
|
{ |
|
slotRelayoutPages(); |
|
slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! |
|
} |
|
} |
|
} |
|
|
|
void PageView::slotToggleForms() |
|
{ |
|
toggleFormWidgets( !d->m_formsVisible ); |
|
} |
|
|
|
void PageView::slotFormChanged( int pageNumber ) |
|
{ |
|
if ( !d->refreshTimer ) |
|
{ |
|
d->refreshTimer = new QTimer( this ); |
|
d->refreshTimer->setSingleShot( true ); |
|
connect( d->refreshTimer, SIGNAL( timeout() ), |
|
this, SLOT( slotRefreshPage() ) ); |
|
} |
|
d->refreshPage = pageNumber; |
|
int delay = 0; |
|
if ( d->m_formsVisible ) |
|
{ |
|
delay = 1000; |
|
} |
|
d->refreshTimer->start( delay ); |
|
} |
|
|
|
void PageView::slotRefreshPage() |
|
{ |
|
const int req = d->refreshPage; |
|
if ( req < 0 ) |
|
return; |
|
d->refreshPage = -1; |
|
QMetaObject::invokeMethod( d->document, "refreshPixmaps", Qt::QueuedConnection, |
|
Q_ARG( int, req ) ); |
|
} |
|
|
|
void PageView::slotSpeakDocument() |
|
{ |
|
QString text; |
|
QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd(); |
|
for ( ; it < itEnd; ++it ) |
|
{ |
|
Okular::RegularAreaRect * area = textSelectionForItem( *it ); |
|
text.append( (*it)->page()->text( area ) ); |
|
text.append( '\n' ); |
|
delete area; |
|
} |
|
|
|
d->tts()->say( text ); |
|
} |
|
|
|
void PageView::slotSpeakCurrentPage() |
|
{ |
|
const int currentPage = d->document->viewport().pageNumber; |
|
|
|
PageViewItem *item = d->items.at( currentPage ); |
|
Okular::RegularAreaRect * area = textSelectionForItem( item ); |
|
const QString text = item->page()->text( area ); |
|
delete area; |
|
|
|
d->tts()->say( text ); |
|
} |
|
|
|
void PageView::slotStopSpeaks() |
|
{ |
|
if ( !d->m_tts ) |
|
return; |
|
|
|
d->m_tts->stopAllSpeechs(); |
|
} |
|
|
|
void PageView::slotAction( Okular::Action *action ) |
|
{ |
|
d->document->processAction( action ); |
|
} |
|
|
|
void PageView::externalKeyPressEvent( QKeyEvent *e ) |
|
{ |
|
keyPressEvent( e ); |
|
} |
|
|
|
void PageView::slotProcessMovieAction( const Okular::MovieAction *action ) |
|
{ |
|
const Okular::MovieAnnotation *movieAnnotation = action->annotation(); |
|
if ( !movieAnnotation ) |
|
return; |
|
|
|
Okular::Movie *movie = movieAnnotation->movie(); |
|
if ( !movie ) |
|
return; |
|
|
|
const int currentPage = d->document->viewport().pageNumber; |
|
|
|
PageViewItem *item = d->items.at( currentPage ); |
|
if ( !item ) |
|
return; |
|
|
|
VideoWidget *vw = item->videoWidgets().value( movie ); |
|
if ( !vw ) |
|
return; |
|
|
|
vw->show(); |
|
|
|
switch ( action->operation() ) |
|
{ |
|
case Okular::MovieAction::Play: |
|
vw->stop(); |
|
vw->play(); |
|
break; |
|
case Okular::MovieAction::Stop: |
|
vw->stop(); |
|
break; |
|
case Okular::MovieAction::Pause: |
|
vw->pause(); |
|
break; |
|
case Okular::MovieAction::Resume: |
|
vw->play(); |
|
break; |
|
}; |
|
} |
|
|
|
void PageView::slotProcessRenditionAction( const Okular::RenditionAction *action ) |
|
{ |
|
Okular::Movie *movie = action->movie(); |
|
if ( !movie ) |
|
return; |
|
|
|
const int currentPage = d->document->viewport().pageNumber; |
|
|
|
PageViewItem *item = d->items.at( currentPage ); |
|
if ( !item ) |
|
return; |
|
|
|
VideoWidget *vw = item->videoWidgets().value( movie ); |
|
if ( !vw ) |
|
return; |
|
|
|
if ( action->operation() == Okular::RenditionAction::None ) |
|
return; |
|
|
|
vw->show(); |
|
|
|
switch ( action->operation() ) |
|
{ |
|
case Okular::RenditionAction::Play: |
|
vw->stop(); |
|
vw->play(); |
|
break; |
|
case Okular::RenditionAction::Stop: |
|
vw->stop(); |
|
break; |
|
case Okular::RenditionAction::Pause: |
|
vw->pause(); |
|
break; |
|
case Okular::RenditionAction::Resume: |
|
vw->play(); |
|
break; |
|
}; |
|
} |
|
|
|
void PageView::slotToggleChangeColors() |
|
{ |
|
Okular::SettingsCore::setChangeColors( !Okular::SettingsCore::changeColors() ); |
|
Okular::Settings::self()->writeConfig(); |
|
viewport()->update(); |
|
} |
|
|
|
//END private SLOTS |
|
|
|
#include "pageview.moc" |
|
|
|
/* kate: replace-tabs on; indent-width 4; */
|
|
|