Add working zoom functions to GraphicsView viewers.

This commit is contained in:
glubsy 2020-06-28 03:31:03 +02:00
parent 9f15139d5f
commit 370b582c9b
2 changed files with 376 additions and 111 deletions

View File

@ -83,7 +83,7 @@ class DetailsDialog(DetailsDialogBase):
# self.horizontalLayout.setColumnStretch(3,0)
self.horizontalLayout.setSpacing(1)
self.selectedImageViewer = ScrollAreaImageViewer(self, "selectedImage")
self.selectedImageViewer = GraphicsViewViewer(self, "selectedImage")
# self.selectedImage = QLabel(self)
# sizePolicy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
# sizePolicy.setHorizontalStretch(0)
@ -100,6 +100,11 @@ class DetailsDialog(DetailsDialogBase):
# self.horizontalLayout.addItem(QSpacerItem(5,0, QSizePolicy.Minimum),
# 1, 3, 1, 1, Qt.Alignment(Qt.AlignRight))
# FIXME make a subclass to initialize buttons later
# FIXME use qwidgetaction to make the popup on resize work -> QWidgetAction::createWidget()
# FIXME figure out why margins are changing when the window is updating (after Normal Size, on resize)
# it seems toggling the scrollbars reduce viewport size and messes up sizeHint returned?
# thus shrinking the space available for the toolbar?
self.verticalToolBar = QToolBar(self)
# self.verticalToolBar.setMaximumWidth(10)
self.verticalToolBar.setOrientation(Qt.Orientation(Qt.Vertical))
@ -151,11 +156,12 @@ class DetailsDialog(DetailsDialogBase):
self.horizontalLayout.addWidget(self.verticalToolBar, 1, 1, 1, 1, Qt.AlignCenter)
self.referenceImageViewer = ScrollAreaImageViewer(self, "referenceImage")
self.referenceImageViewer = GraphicsViewViewer(self, "referenceImage")
# self.referenceImage = QLabel(self)
# sizePolicy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
# sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
# sizePolicy.setHorizontalStretch(0)
# sizePolicy.setVerticalStretch(0)
# self.verticalToolBar.setSizePolicy(sizePolicy)
# sizePolicy.setHeightForWidth(
# self.referenceImage.sizePolicy().hasHeightForWidth()
# )
@ -217,6 +223,8 @@ class DetailsDialog(DetailsDialogBase):
self)
def _update(self):
if self.vController is None: # Not yet constructed!
return
if not self.app.model.selected_dupes:
# No item from the model, disable and clear everything.
self.vController.resetViewersState()
@ -225,8 +233,6 @@ class DetailsDialog(DetailsDialogBase):
group = self.app.model.results.get_group_of_duplicate(dupe)
ref = group.ref
if self.vController is None: # Not yet constructed!
return
self.vController.updateView(ref, dupe, group)
# --- Override
@ -277,11 +283,11 @@ class DetailsDialog(DetailsDialogBase):
@pyqtSlot()
def zoomIn(self):
self.vController.scaleImagesBy(1.25)
self.vController.zoomIn()
@pyqtSlot()
def zoomOut(self):
self.vController.scaleImagesBy(0.8)
self.vController.zoomOut()
@pyqtSlot()
def zoomBestFit(self):

View File

@ -3,7 +3,7 @@
# http://www.gnu.org/licenses/gpl-3.0.html
from PyQt5.QtCore import QObject, Qt, QSize, QRectF, QPointF, QPoint, pyqtSlot, pyqtSignal, QEvent
from PyQt5.QtGui import QPixmap, QPainter, QPalette, QCursor
from PyQt5.QtGui import QPixmap, QPainter, QPalette, QCursor, QTransform
from PyQt5.QtWidgets import ( QLabel, QSizePolicy, QWidget, QScrollArea,
QScrollBar, QApplication, QAbstractScrollArea )
@ -62,6 +62,7 @@ class BaseController(QObject):
self.centerViews(same_group and self.referencePixmap.isNull())
def updateBothImages(self, same_group=False):
# FIXME this is called on every resize event,
ignore_update = self.referencePixmap.isNull()
if ignore_update:
self.selectedViewer.ignore_signal = True
@ -79,7 +80,7 @@ class BaseController(QObject):
self.selectedViewer.ignore_signal = False
def _updateImage(self, pixmap, scaledpixmap, viewer, target_size=None, same_group=False):
# If not same_group, we need full update"""
# FIXME this is called on every resize event, split into a separate function
if pixmap.isNull():
# disable the blank widget.
viewer.setImage(pixmap)
@ -91,6 +92,7 @@ class BaseController(QObject):
viewer.setImage(pixmap)
return target_size
# zoomed in state, expand
# only if not same_group, we need full update
scaledpixmap = pixmap.scaled(
target_size, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)
else:
@ -109,8 +111,15 @@ class BaseController(QObject):
self.scaledReferencePixmap = QPixmap()
self.setBestFit(True)
self.current_scale = 1.0
self.selectedViewer.current_scale = 1.0
self.referenceViewer.current_scale = 1.0
self.selectedViewer.resetCenter()
self.referenceViewer.resetCenter()
self.selectedViewer.scaleAt(1.0)
self.referenceViewer.scaleAt(1.0)
self.centerViews()
#FIXME move buttons somwhere else
@ -128,8 +137,12 @@ class BaseController(QObject):
self.scaledReferencePixmap = QPixmap()
self.setBestFit(True)
self.current_scale = 1.0
self.selectedViewer.current_scale = 1.0
self.referenceViewer.current_scale = 1.0
self.selectedViewer.resetCenter()
self.referenceViewer.resetCenter()
self.selectedViewer.scaleAt(1.0)
self.referenceViewer.scaleAt(1.0)
self.centerViews()
#FIXME move buttons somwhere else
@ -144,6 +157,14 @@ class BaseController(QObject):
self.referenceViewer.setImage(self.referencePixmap) # null
self.referenceViewer.setEnabled(False)
@pyqtSlot()
def zoomIn(self):
self.scaleImagesBy(1.25)
@pyqtSlot()
def zoomOut(self):
self.scaleImagesBy(0.8)
@pyqtSlot(float)
def scaleImagesBy(self, factor):
"""Compute new scale from factor and scale."""
@ -171,6 +192,8 @@ class BaseController(QObject):
"""Setup before scaling to bestfit"""
self.setBestFit(True)
self.current_scale = 1.0
self.selectedViewer.current_scale = 1.0
self.referenceViewer.current_scale = 1.0
self.selectedViewer.scaleAt(1.0)
self.referenceViewer.scaleAt(1.0)
@ -301,7 +324,7 @@ class ScrollAreaController(BaseController):
def scaleImagesBy(self, factor):
super().scaleImagesBy(factor)
# The other is automatically updated via sigals
self.selectedViewer.adjustScrollBarsFactor(factor)
# self.selectedViewer.adjustScrollBarsFactor(factor)
@pyqtSlot()
def ScaleToBestFit(self):
@ -318,6 +341,11 @@ class GraphicsViewController(BaseController):
def __init__(self, selectedViewer, referenceViewer, parent):
super().__init__(selectedViewer, referenceViewer, parent)
def _setupConnections(self):
super()._setupConnections()
self.selectedViewer.connectScrollBars()
self.referenceViewer.connectScrollBars()
@pyqtSlot()
def syncCenters(self):
if self.sender() is self.referenceViewer:
@ -325,6 +353,179 @@ class GraphicsViewController(BaseController):
else:
self.referenceViewer.setCenter(self.selectedViewer.getCenter())
@pyqtSlot(float, QPointF)
def onMouseWheel(self, factor, newCenter):
self.current_scale *= factor
if self.sender() is self.referenceViewer:
self.selectedViewer.scaleBy(factor)
self.selectedViewer.setCenter(self.referenceViewer.getCenter())
else:
self.referenceViewer.scaleBy(factor)
self.referenceViewer.setCenter(self.selectedViewer.getCenter())
# self.selectedViewer.adjustScrollBarsScaled(delta)
# Signal from scrollbars will automatically change the other:
# self.referenceViewer.adjustScrollBarsScaled(delta)
@pyqtSlot(int)
def onVScrollBarChanged(self, value):
if self.sender() is self.referenceViewer._verticalScrollBar:
if not self.selectedViewer.ignore_signal:
self.selectedViewer._verticalScrollBar.setValue(value)
else:
if not self.referenceViewer.ignore_signal:
self.referenceViewer._verticalScrollBar.setValue(value)
@pyqtSlot(int)
def onHScrollBarChanged(self, value):
if self.sender() is self.referenceViewer._horizontalScrollBar:
if not self.selectedViewer.ignore_signal:
self.selectedViewer._horizontalScrollBar.setValue(value)
else:
if not self.referenceViewer.ignore_signal:
self.referenceViewer._horizontalScrollBar.setValue(value)
@pyqtSlot()
def swapPixmaps(self):
self.referenceViewer._pixmap.swap(self.selectedViewer._pixmap)
self.referenceViewer.setCachedPixmap()
self.selectedViewer.setCachedPixmap()
@pyqtSlot()
def ScaleToBestFit(self):
"""Setup before scaling to bestfit"""
self.setBestFit(True)
self.current_scale = 1.0
self.selectedViewer.fitScale()
self.referenceViewer.fitScale()
self.parent.buttonBestFit.setEnabled(False)
self.parent.buttonZoomOut.setEnabled(False)
self.parent.buttonZoomIn.setEnabled(False)
self.parent.buttonNormalSize.setEnabled(True)
def updateView(self, ref, dupe, group):
# Keep current scale accross dupes from the same group
same_group = True
if group != self.cached_group:
same_group = False
self.resetState()
self.cached_group = group
self.selectedPixmap = QPixmap(str(dupe.path))
if ref is dupe: # currently selected file is the actual reference file
self.referencePixmap = QPixmap()
self.parent.buttonImgSwap.setEnabled(False)
self.parent.buttonNormalSize.setEnabled(True)
else:
self.referencePixmap = QPixmap(str(ref.path))
self.parent.buttonImgSwap.setEnabled(True)
self.parent.buttonNormalSize.setEnabled(True)
self.selectedViewer.setImage(self.selectedPixmap)
self.referenceViewer.setImage(self.referencePixmap)
self.updateBothImages(same_group)
self.centerViews(same_group and self.referencePixmap.isNull())
def updateBothImages(self, same_group=False):
"""This is called only during resize events and while bestFit."""
ignore_update = self.referencePixmap.isNull()
if ignore_update:
self.selectedViewer.ignore_signal = True
self._updateFitImage(
self.selectedPixmap, self.selectedViewer)
self._updateFitImage(
self.referencePixmap, self.referenceViewer)
if ignore_update:
self.selectedViewer.ignore_signal = False
def _updateFitImage(self, pixmap, viewer):
# If not same_group, we need full update"""
if pixmap.isNull():
return
if viewer.bestFit:
viewer.fitScale()
def resetState(self):
"""Only called when the group of dupes has changed. We reset our
controller internal state and buttons, center view on viewers."""
self.selectedPixmap = QPixmap()
self.referencePixmap = QPixmap()
self.setBestFit(True)
self.current_scale = 1.0
self.selectedViewer.current_scale = 1.0
self.referenceViewer.current_scale = 1.0
self.selectedViewer.resetCenter()
self.referenceViewer.resetCenter()
self.selectedViewer.fitScale()
self.referenceViewer.fitScale()
# self.centerViews()
#FIXME move buttons somwhere else
self.parent.buttonZoomIn.setEnabled(False)
self.parent.buttonZoomOut.setEnabled(False)
self.parent.buttonBestFit.setEnabled(False) # active mode by default
self.parent.buttonNormalSize.setEnabled(True)
def resetViewersState(self):
"""No item from the model, disable and clear everything."""
# only called by the details dialog
self.selectedPixmap = QPixmap()
self.scaledSelectedPixmap = QPixmap()
self.referencePixmap = QPixmap()
self.scaledReferencePixmap = QPixmap()
self.setBestFit(True)
self.current_scale = 1.0
self.selectedViewer.current_scale = 1.0
self.referenceViewer.current_scale = 1.0
self.selectedViewer.resetCenter()
self.referenceViewer.resetCenter()
# self.centerViews()
#FIXME move buttons somwhere else
self.parent.buttonZoomIn.setEnabled(False)
self.parent.buttonZoomOut.setEnabled(False)
self.parent.buttonBestFit.setEnabled(False) # active mode by default
self.parent.buttonImgSwap.setEnabled(False)
self.parent.buttonNormalSize.setEnabled(False)
self.selectedViewer.setImage(self.selectedPixmap) # null
self.selectedViewer.setEnabled(False)
self.referenceViewer.setImage(self.referencePixmap) # null
self.referenceViewer.setEnabled(False)
@pyqtSlot(float)
def scaleImagesBy(self, factor):
self.selectedViewer.updateCenterPoint()
self.referenceViewer.updateCenterPoint()
super().scaleImagesBy(factor)
# self.selectedViewer.setNewCenter(self.selectedViewer._scene.sceneRect().center())
# self.selectedViewer._centerPoint = self.selectedViewer.viewport().rect().center()
# self.referenceViewer._mousePanningDelta = self.selectedViewer._mousePanningDelta
# # self.selectedViewer._mousePanningDelta = self.referenceViewer._mousePanningDelta
# self.selectedViewer.adjustScrollBarsAuto()
# self.referenceViewer.adjustScrollBarsAuto()
self.selectedViewer.centerOn(self.selectedViewer._centerPoint)
# self.selectedViewer.updateCenterPoint()
# self.referenceViewer.setCenter(self.selectedViewer.getCenter())
# self.selectedViewer.setCenter(self.referenceViewer.getCenter())
# self.referenceViewer.setCenter(self.selectedViewer.getCenter())
# The other is automatically updated via sigals
# self.selectedViewer.adjustScrollBarsFactor(factor)
class QWidgetImageViewer(QWidget):
"""Use a QPixmap, but no scrollbars."""
@ -340,7 +541,7 @@ class QWidgetImageViewer(QWidget):
self._rect = QRectF()
self._lastMouseClickPoint = QPointF()
self._mousePanningDelta = QPointF()
self._current_scale = 1.0
self.current_scale = 1.0
self._drag = False
self._dragConnection = None
self._wheelConnection = None
@ -374,7 +575,7 @@ class QWidgetImageViewer(QWidget):
def paintEvent(self, event):
painter = QPainter(self)
painter.translate(self.rect().center())
painter.scale(self._current_scale, self._current_scale)
painter.scale(self.current_scale, self.current_scale)
painter.translate(self._mousePanningDelta)
painter.drawPixmap(self._rect.topLeft(), self._pixmap)
@ -382,7 +583,6 @@ class QWidgetImageViewer(QWidget):
""" Resets origin """
# Make sure we are not still panning around
self._mousePanningDelta = QPointF()
self.scaleAt(1.0)
self.update()
def changeEvent(self, event):
@ -415,7 +615,7 @@ class QWidgetImageViewer(QWidget):
return
self._mousePanningDelta += (event.pos() - self._lastMouseClickPoint) \
* 1.0 / self._current_scale
* 1.0 / self.current_scale
self._lastMouseClickPoint = event.pos()
if self._drag:
self.mouseDragged.emit(self._mousePanningDelta)
@ -437,11 +637,11 @@ class QWidgetImageViewer(QWidget):
return
if event.angleDelta().y() > 0:
if self._current_scale > MAX_SCALE:
if self.current_scale > MAX_SCALE:
return
self.mouseWheeled.emit(1.25) # zoom-in
else:
if self._current_scale < MIN_SCALE:
if self.current_scale < MIN_SCALE:
return
self.mouseWheeled.emit(0.8) # zoom-out
@ -467,11 +667,11 @@ class QWidgetImageViewer(QWidget):
return True if not self.pixmap.isNull() else False
def scaleBy(self, factor):
self._current_scale *= factor
self.current_scale *= factor
self.update()
def scaleAt(self, scale):
self._current_scale = scale
self.current_scale = scale
self.update()
def sizeHint(self):
@ -480,7 +680,7 @@ class QWidgetImageViewer(QWidget):
@pyqtSlot()
def scaleToNormalSize(self):
"""Called when the pixmap is set back to original size."""
self._current_scale = 1.0
self.current_scale = 1.0
self.update()
@pyqtSlot(QPointF)
@ -495,23 +695,23 @@ class ScalablePixmap(QWidget):
def __init__(self, parent):
super().__init__(parent)
self._pixmap = QPixmap()
self._current_scale = 1.0
self.current_scale = 1.0
def paintEvent(self, event):
painter = QPainter(self)
# painter.translate(self.rect().center())
# painter.setRenderHint(QPainter.Antialiasing, False)
# scale the coordinate system:
painter.scale(self._current_scale, self._current_scale)
painter.scale(self.current_scale, self.current_scale)
painter.drawPixmap(self.rect().topLeft(), self._pixmap) #same as (0,0, self.pixmap)
# print(f"ScalableWidget paintEvent scale {self._current_scale}")
# print(f"ScalableWidget paintEvent scale {self.current_scale}")
def setPixmap(self, pixmap):
self._pixmap = pixmap
# self.update()
def sizeHint(self):
return self._pixmap.size() * self._current_scale
return self._pixmap.size() * self.current_scale
# return self._pixmap.size()
def minimumSizeHint(self):
@ -535,7 +735,7 @@ class ScrollAreaImageViewer(QScrollArea):
self._rect = QRectF()
self._lastMouseClickPoint = QPointF()
self._mousePanningDelta = QPoint()
self._current_scale = 1.0
self.current_scale = 1.0
self._drag = False
self._dragConnection = None
self._wheelConnection = None
@ -606,6 +806,7 @@ class ScrollAreaImageViewer(QScrollArea):
def connectScrollBars(self):
"""Only call once controller is connected."""
# Cyclic connections are handled by Qt
return
self._verticalScrollBar.valueChanged.connect(
self.controller.onVScrollBarChanged, Qt.UniqueConnection)
self._horizontalScrollBar.valueChanged.connect(
@ -651,19 +852,19 @@ class ScrollAreaImageViewer(QScrollArea):
if self.bestFit:
event.ignore()
return
oldScale = self._current_scale
oldScale = self.current_scale
if event.angleDelta().y() > 0: # zoom-in
if oldScale < MAX_SCALE:
self._current_scale *= 1.25
self.current_scale *= 1.25
else:
if oldScale > MIN_SCALE: # zoom-out
self._current_scale *= 0.8
if oldScale == self._current_scale:
self.current_scale *= 0.8
if oldScale == self.current_scale:
return
deltaToPos = (event.position() / oldScale) - (self.label.pos() / oldScale)
delta = (deltaToPos * self._current_scale) - (deltaToPos * oldScale)
self.mouseWheeled.emit(self._current_scale, delta)
delta = (deltaToPos * self.current_scale) - (deltaToPos * oldScale)
self.mouseWheeled.emit(self.current_scale, delta)
def setImage(self, pixmap):
self._pixmap = pixmap
@ -680,7 +881,7 @@ class ScrollAreaImageViewer(QScrollArea):
def centerViewAndUpdate(self):
self._rect = self.label.rect()
self.label.rect().translate(-self._rect.center())
self.label._current_scale = self._current_scale
self.label.current_scale = self.current_scale
self.label.update()
# self.viewport().update()
@ -693,8 +894,8 @@ class ScrollAreaImageViewer(QScrollArea):
return True if not self.pixmap.isNull() else False
def scaleBy(self, factor):
self._current_scale *= factor
# print(f"scaleBy(factor={factor}) current_scale={self._current_scale}")
self.current_scale *= factor
# print(f"scaleBy(factor={factor}) current_scale={self.current_scale}")
# This kills my computer when scaling up! DO NOT USE!
# self._pixmap = self._pixmap.scaled(
@ -713,7 +914,7 @@ class ScrollAreaImageViewer(QScrollArea):
self.label.resize(self.label.size().__imul__(factor))
# self.label.updateGeometry()
self.label._current_scale = self._current_scale
self.label.current_scale = self.current_scale
self.label.update()
# Center view on zoom change(?) same as imageLabel->resize(imageLabel->pixmap()->size())
# self.label.adjustSize()
@ -732,9 +933,9 @@ class ScrollAreaImageViewer(QScrollArea):
# self.adjustScrollBarCentered()
def scaleAt(self, scale):
self._current_scale = scale
self.current_scale = scale
self.label.resize(self._pixmap.size().__imul__(scale))
self.label._current_scale = scale
self.label.current_scale = scale
self.label.update()
# self.label.adjustSize()
@ -783,7 +984,7 @@ class ScrollAreaImageViewer(QScrollArea):
def resetCenter(self):
""" Resets origin """
self._mousePanningDelta = QPoint()
self._current_scale = 1.0
self.current_scale = 1.0
# self.scaleBy(1.0)
# self.label.update() # already called in scaleBy
@ -794,10 +995,10 @@ class ScrollAreaImageViewer(QScrollArea):
return self._lastMouseClickPoint
def sizeHint(self):
return self._pixmap.rect().size()
return self.viewport().rect().size()
def viewportSizeHint(self):
return self._pixmap.rect().size()
# def viewportSizeHint(self):
# return self.viewport().rect().size()
@pyqtSlot()
def scaleToNormalSize(self):
@ -827,9 +1028,9 @@ class ScrollAreaImageViewer(QScrollArea):
from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem
class GraphicsViewViewer(QGraphicsView):
"""Re-Implementation."""
"""Re-Implementation using a more full fledged class."""
mouseDragged = pyqtSignal()
mouseWheeled = pyqtSignal(bool)
mouseWheeled = pyqtSignal(float, QPointF)
def __init__(self, parent, name=""):
super().__init__(parent)
@ -841,7 +1042,9 @@ class GraphicsViewViewer(QGraphicsView):
self._lastMouseClickPoint = QPointF()
self._mousePanningDelta = QPointF()
self._scaleFactor = 1.3
self._current_scale = 1.0
self.zoomInFactor = self._scaleFactor
self.zoomOutFactor = 1.0 / self._scaleFactor
self.current_scale = 1.0
self._drag = False
self._dragConnection = None
self._wheelConnection = None
@ -849,25 +1052,29 @@ class GraphicsViewViewer(QGraphicsView):
self.wantScrollBars = True
self.bestFit = True
self.controller = None
self._centerPoint = QPointF(0.0, 0.0)
self._centerPoint = QPointF()
self.centerOn(self._centerPoint)
# specific to this class
self._scene = QGraphicsScene()
self._scene.setBackgroundBrush(Qt.black)
self._item = QGraphicsPixmapItem()
self.setScene(self._scene)
self._scene.addItem(self._item)
self.setDragMode(QGraphicsView.DragMode.ScrollHandDrag)
self.matrix = QTransform()
self._horizontalScrollBar = self.horizontalScrollBar()
self._verticalScrollBar = self.verticalScrollBar()
self.ignore_signal = False
if self.wantScrollBars:
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.toggleScrollBars()
else:
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setResizeAnchor(QGraphicsView.AnchorViewCenter)
self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
self.setAlignment(Qt.AlignCenter)
self.setViewportUpdateMode (QGraphicsView.FullViewportUpdate)
self.setMouseTracking(True)
@ -877,7 +1084,7 @@ class GraphicsViewViewer(QGraphicsView):
self.controller.syncCenters)
if not self._wheelConnection:
self._wheelConnection = self.mouseWheeled.connect(
self.controller.scaleImages)
self.controller.onMouseWheel)
def disconnectMouseSignals(self):
if self._dragConnection:
@ -887,6 +1094,27 @@ class GraphicsViewViewer(QGraphicsView):
self.mouseWheeled.disconnect()
self._wheelConnection = None
def connectScrollBars(self):
"""Only call once controller is connected."""
# Cyclic connections are handled by Qt
self._verticalScrollBar.valueChanged.connect(
self.controller.onVScrollBarChanged, Qt.UniqueConnection)
self._horizontalScrollBar.valueChanged.connect(
self.controller.onHScrollBarChanged, Qt.UniqueConnection)
def toggleScrollBars(self, forceOn=False):
if not self.wantScrollBars:
return
# Ensure that it's off on the first run
if self.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded:
if forceOn:
return
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
else:
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
def mousePressEvent(self, event):
if self.bestFit:
event.ignore()
@ -897,7 +1125,6 @@ class GraphicsViewViewer(QGraphicsView):
self._drag = False
event.ignore()
return
self._lastMouseClickPoint = event.pos()
self._app.setOverrideCursor(Qt.ClosedHandCursor)
self.setMouseTracking(True)
@ -905,40 +1132,53 @@ class GraphicsViewViewer(QGraphicsView):
super().mousePressEvent(event)
# event.accept()
def mouseMoveEvent(self, event):
if self.bestFit:
event.ignore()
return
self._centerPoint = self.mapToScene( self.rect().center() )
super().mouseMoveEvent(event)
if self._drag:
self.mouseDragged.emit()
# self._item.update()
def mouseReleaseEvent(self, event):
if self.bestFit:
event.ignore()
return
if event.button() == Qt.LeftButton:
self._drag = False
self._app.restoreOverrideCursor()
self.setMouseTracking(False)
self.updateCenterPoint()
super().mouseReleaseEvent(event)
def wheelEvent(self, event):
def mouseMoveEvent(self, event):
if self.bestFit:
event.ignore()
return
if self._drag:
delta = (event.pos() - self._lastMouseClickPoint)
self._lastMouseClickPoint = event.pos()
# We can simply rely on the scrollbar updating each other here
# self.mouseDragged.emit()
self.updateCenterPoint()
super().mouseMoveEvent(event)
def updateCenterPoint(self):
self._centerPoint = self.mapToScene( self.viewport().rect().center())
def wheelEvent(self, event):
if self.bestFit or MIN_SCALE > self.current_scale > MAX_SCALE:
event.ignore()
return
pointBeforeScale = QPointF(self.mapToScene(self.mapFromGlobal(QCursor.pos())))
# Get the original screen centerpoint
screenCenter = QPointF(self.mapToScene( self.rect().center() ))
if event.angleDelta().y() > 0:
self.mouseWheeled.emit(True) # zoom-in
factor = self.zoomInFactor
else:
self.mouseWheeled.emit(False) # zoom-out
factor = self.zoomOutFactor
self.scaleBy(factor)
pointAfterScale = QPointF(self.mapToScene( self.mapFromGlobal(QCursor.pos())))
#Get the offset of how the screen moved
offset = pointBeforeScale - pointAfterScale
#Adjust to the new center for correct zooming
newCenter = screenCenter + offset
self.setCenter(newCenter)
self.mouseWheeled.emit(factor, newCenter)
def setImage(self, pixmap):
self._pixmap = pixmap
@ -947,14 +1187,15 @@ class GraphicsViewViewer(QGraphicsView):
# self._item.setOffset(offset)
# self.setSceneRect(offset.x()*4, offset.y()*4, -offset.x()*8, -offset.y()*8)
self.translate(1, 1)
# self._scene.setSceneRect(self._pixmap.rect())
# self._scene.setSceneRect(QRectF(self._pixmap.rect())) # not sure if this works
def centerViewAndUpdate(self):
# self._rect = self.sceneRect()
# self._rect.translate(-self._rect.center())
# self._item.update()
# self.viewport().update()
pass
def scaleBy(self, factor):
# super().scale(factor, factor)
self.zoom(factor)
def setCenter(self, point):
self._centerPoint = point
@ -966,59 +1207,77 @@ class GraphicsViewViewer(QGraphicsView):
def resetCenter(self):
""" Resets origin """
self._mousePanningDelta = QPointF()
self._current_scale = 1.0
self.scaleBy(1.0)
self.current_scale = 1.0
# self.update()
self.setCenter(self._scene.sceneRect().center())
# self.setCenter(self._scene.sceneRect().center())
def setNewCenter(self, position):
self._centerPoint = position
self.centerOn(self._centerPoint)
def setCachedPixmap(self):
"""In case we have changed the cached pixmap, reset it."""
self._item.setPixmap(self._pixmap)
self._item.update()
def scaleAt(self, scale):
# self.current_scale = scale
if scale == 1.0:
self.resetScale()
# self.setTransform( QTransform() )
self.scale(scale, scale)
def getScale(self):
return self.transform().m22()
def scaleBy(self, factor):
self.current_scale *= factor
super().scale(factor, factor)
def resetScale(self):
# self.setTransform( QTransform() )
self.resetTransform() # probably same as above
self.setCenter( self.scene().sceneRect().center() )
# self.scaleChanged.emit( self.transform().m22() )
def fitScale(self):
self.bestFit = True
super().fitInView(self._scene.sceneRect(), Qt.KeepAspectRatio )
self.setNewCenter(self._scene.sceneRect().center())
@pyqtSlot()
def scaleToNormalSize(self):
"""Called when the pixmap is set back to original size."""
self.scaleBy(1.0) # FIXME
super().fitInView(self._scene.sceneRect(), Qt.KeepAspectRatio )
self.setNewCenter(self._scene.sceneRect().center())
self.bestFit = False
self.scaleAt(1.0)
self.toggleScrollBars()
self.update()
# def sizeHint(self):
# return self._item.rect().size()
def adjustScrollBarsScaled(self, delta):
"""After scaling with the mouse, update relative to mouse position."""
self._horizontalScrollBar.setValue(
self._horizontalScrollBar.value() + delta.x())
self._verticalScrollBar.setValue(
self._verticalScrollBar.value() + delta.y())
def sizeHint(self):
return self.viewport().rect().size()
# def viewportSizeHint(self):
# return self._item.rect().size()
# return self.viewport().rect().size()
def zoom_in(self):
self.zoom(self._scaleFactor)
def zoom_out(self):
self.zoom(1.0 / self._scaleFactor)
def zoom(self, factor):
#Get the position of the mouse before scaling, in scene coords
pointBeforeScale = QPointF(self.mapToScene(self.mapFromGlobal(QCursor.pos())))
#Get the original screen centerpoint
screenCenter = self.mapToScene( self.rect().center() )
super().scale(factor, factor)
#Get the position after scaling, in scene coords
pointAfterScale = QPointF( self.mapToScene( self.mapFromGlobal(QCursor.pos()) ) )
#Get the offset of how the screen moved
offset = QPointF( pointBeforeScale - pointAfterScale)
#Adjust to the new center for correct zooming
newCenter = QPointF(screenCenter + offset)
self.setNewCenter(newCenter)
# self.updateSceneRect(self._item.rect()) # TEST THIS?
# mouse position has changed!!
# emit mouseMoved( QGraphicsView::mapToScene( event->pos() ) );
# emit mouseMoved( QGraphicsView::mapToScene( mapFromGlobal(QCursor::pos()) ) );
# emit somethingChanged();
def adjustScrollBarsFactor(self, factor):
"""After scaling, no mouse position, default to center."""
# scrollBar.setMaximum(scrollBar.maximum() - scrollBar.minimum() + scrollBar.pageStep())
self._horizontalScrollBar.setValue(int(factor * self._horizontalScrollBar.value() + \
((factor - 1) * self._horizontalScrollBar.pageStep()/2)))
self._verticalScrollBar.setValue(int(factor * self._verticalScrollBar.value() + \
((factor - 1) * self._verticalScrollBar.pageStep()/2)))
def adjustScrollBarsAuto(self):
"""After panning, update accordingly."""
self.horizontalScrollBar().setValue(
self.horizontalScrollBar().value() - self._mousePanningDelta.x())
self.verticalScrollBar().setValue(
self.verticalScrollBar().value() - self._mousePanningDelta.y())