--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/PdfViewer/PdfView.py Wed Jan 18 14:31:55 2023 +0100 @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a specialized PDF view class. +""" + +from PyQt6.QtCore import QSize, Qt, pyqtSlot, QEvent +from PyQt6.QtGui import QGuiApplication +from PyQt6.QtPdfWidgets import QPdfView + +from .PdfZoomSelector import PdfZoomSelector + + +class PdfView(QPdfView): + """ + Class implementing a specialized PDF view. + """ + + def __init__(self, parent): + """ + Constructor + + @param parent reference to the parent widget + @type QWidget + """ + super().__init__(parent) + + self.__screenResolution = ( + QGuiApplication.primaryScreen().logicalDotsPerInch() / 72.0 + ) + + self.grabGesture(Qt.GestureType.PinchGesture) + + def __zoomInOut(self, zoomIn): + """ + Private method to zoom into or out of the view. + + @param zoomIn flag indicating to zoom into the view + @type bool + """ + zoomFactor = self.__zoomFactorForMode(self.zoomMode()) + + factors = list(PdfZoomSelector.ZoomValues) + factors.append(self.__zoomFactorForMode(QPdfView.ZoomMode.FitInView)) + factors.append(self.__zoomFactorForMode(QPdfView.ZoomMode.FitToWidth)) + if zoomIn: + factors.sort() + if zoomFactor >= factors[-1]: + return + newIndex = next(x for x, val in enumerate(factors) if val > zoomFactor) + else: + factors.sort(reverse=True) + if zoomFactor <= factors[-1]: + return + newIndex = next(x for x, val in enumerate(factors) if val < zoomFactor) + newFactor = factors[newIndex] + if newFactor == self.__zoomFactorForMode(QPdfView.ZoomMode.FitInView): + self.setZoomMode(QPdfView.ZoomMode.FitInView) + self.zoomModeChanged.emit(QPdfView.ZoomMode.FitInView) + elif newFactor == self.__zoomFactorForMode(QPdfView.ZoomMode.FitToWidth): + self.setZoomMode(QPdfView.ZoomMode.FitToWidth) + self.zoomModeChanged.emit(QPdfView.ZoomMode.FitToWidth) + else: + self.setZoomFactor(newFactor) + self.zoomFactorChanged.emit(newFactor) + self.setZoomMode(QPdfView.ZoomMode.Custom) + self.zoomModeChanged.emit(QPdfView.ZoomMode.Custom) + + def __zoomFactorForMode(self, zoomMode): + """ + Private method to calculate the zoom factor iaw. the current zoom mode. + + @param zoomMode zoom mode to get the zoom factor for + @type QPdfView.ZoomMode + @return zoom factor + @rtype float + """ + if zoomMode == QPdfView.ZoomMode.Custom: + return self.zoomFactor() + else: + viewport = self.viewport() + curPage = self.pageNavigator().currentPage() + margins = self.documentMargins() + if zoomMode == QPdfView.ZoomMode.FitToWidth: + pageSize = ( + self.document().pagePointSize(curPage) * self.__screenResolution + ).toSize() + factor = ( + viewport.width() - margins.left() - margins.right() + ) / pageSize.width() + pageSize *= factor + else: + # QPdfView.ZoomMode.FitInView + viewportSize = viewport.size() + QSize( + -margins.left() - margins.right(), -self.pageSpacing() + ) + pageSize = ( + self.document().pagePointSize(curPage) * self.__screenResolution + ).toSize() + pageSize = pageSize.scaled( + viewportSize, Qt.AspectRatioMode.KeepAspectRatio + ) + zoomFactor = pageSize.width() / ( + self.document().pagePointSize(curPage) * self.__screenResolution + ).width() + return zoomFactor + + @pyqtSlot() + def zoomIn(self): + """ + Public slot to zoom into the view. + """ + self.__zoomInOut(True) + + @pyqtSlot() + def zoomOut(self): + """ + Public slot to zoom out of the view. + """ + self.__zoomInOut(False) + + @pyqtSlot() + def zoomReset(self): + """ + Public slot to reset the zoom factor of the view. + """ + if self.zoomMode() != QPdfView.ZoomMode.Custom or self.zoomFactor() != 1.0: + self.setZoomFactor(1.0) + self.zoomFactorChanged.emit(1.0) + self.setZoomMode(QPdfView.ZoomMode.Custom) + self.zoomModeChanged.emit(QPdfView.ZoomMode.Custom) + + def wheelEvent(self, evt): + """ + Protected method to handle wheel events. + + @param evt reference to the wheel event + @type QWheelEvent + """ + delta = evt.angleDelta().y() + if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: + if delta < 0: + self.zoomOut() + elif delta > 0: + self.zoomIn() + evt.accept() + return + + elif evt.modifiers() & Qt.KeyboardModifier.ShiftModifier: + if delta < 0: + self.pageNavigator().back() + elif delta > 0: + self.pageNavigator().forward() + evt.accept() + return + + super().wheelEvent(evt) + + def event(self, evt): + """ + Public method handling events. + + @param evt reference to the event + @type QEvent + @return flag indicating, if the event was handled + @rtype bool + """ + if evt.type() == QEvent.Type.Gesture: + self.gestureEvent(evt) + return True + + return super().event(evt) + + def gestureEvent(self, evt): + """ + Protected method handling gesture events. + + @param evt reference to the gesture event + @type QGestureEvent + """ + pinch = evt.gesture(Qt.GestureType.PinchGesture) + if pinch: + if pinch.state() == Qt.GestureState.GestureStarted: + pinch.setTotalScaleFactor(self.__zoomFactorForMode(self.zoomMode())) + elif pinch.state() == Qt.GestureState.GestureUpdated: + if self.zoomMode() != QPdfView.ZoomMode.Custom: + self.setZoomMode(QPdfView.ZoomMode.Custom) + self.zoomModeChanged.emit(QPdfView.ZoomMode.Custom) + zoomFactor = pinch.totalScaleFactor() + self.setZoomFactor(zoomFactor) + self.zoomFactorChanged.emit(zoomFactor) + evt.accept()