I am trying to adapt a photo viewer in pyqt4 (I got the code from another question on this site). By default, the mouse wheel zooms, and the left mouse button is used to pan/drag (don't know exactly what to call this) the image. I want to add the capability to get pixel information when left-clicking on the image, so I intended to have a button to change from 'pan' mode to 'pixel info' mode. My attempt to do this is below but it is not behaving as I intended.
When I load an image with the first GUI button and zoom in on the image, if I don't pan/drag the image, then clicking the second GUI button disables the pan/drag and, when I click on the image, provides pixel info for pixel where I clicked. This is what I expected.
However if I zoom in and then pan/drag the image first, when I click on the second GUI button, it no longer disables the pan/drag and doesn't print pixel info.
For some reason I don't understand (and which I hope somebody can enlighten me with) dragging the image after zooming seems to lock in the pan/drag method and not allow me to change it. Can anyone tell me why.
from PyQt4 import QtCore, QtGui
class PhotoViewer(QtGui.QGraphicsView):
def __init__(self, parent):
super(PhotoViewer, self).__init__(parent)
self._zoom = 0
self._scene = QtGui.QGraphicsScene(self)
self._photo = QtGui.QGraphicsPixmapItem()
self._scene.addItem(self._photo)
self.setScene(self._scene)
self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30)))
self.setFrameShape(QtGui.QFrame.NoFrame)
def fitInView(self):
rect = QtCore.QRectF(self._photo.pixmap().rect())
if not rect.isNull():
unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
self.scale(1 / unity.width(), 1 / unity.height())
viewrect = self.viewport().rect()
scenerect = self.transform().mapRect(rect)
factor = min(viewrect.width() / scenerect.width(),
viewrect.height() / scenerect.height())
self.scale(factor, factor)
self.centerOn(rect.center())
self._zoom = 0
def setPhoto(self, pixmap=None):
self._zoom = 0
if pixmap and not pixmap.isNull():
self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)
self._photo.setPixmap(pixmap)
self.fitInView()
else:
self.setDragMode(QtGui.QGraphicsView.NoDrag)
self._photo.setPixmap(QtGui.QPixmap())
def wheelEvent(self, event):
if not self._photo.pixmap().isNull():
if event.delta() > 0:
factor = 1.25
self._zoom += 1
else:
factor = 0.8
self._zoom -= 1
if self._zoom > 0:
self.scale(factor, factor)
elif self._zoom == 0:
self.fitInView()
else:
self._zoom = 0
def printPixInfo(self,event):
print event.pos()
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.viewer = PhotoViewer(self)
# 'Load image' button
self.btnLoad = QtGui.QToolButton(self)
self.btnLoad.setText('Load image')
self.btnLoad.clicked.connect(self.loadImage)
# Button to change from drag/pan to getting pixel info
self.btnPixInfo = QtGui.QToolButton(self)
self.btnPixInfo.setText('Enter pixel info mode')
self.btnPixInfo.clicked.connect(self.pixInfo)
# Arrange layout
VBlayout = QtGui.QVBoxLayout(self)
VBlayout.addWidget(self.viewer)
HBlayout = QtGui.QHBoxLayout()
HBlayout.setAlignment(QtCore.Qt.AlignLeft)
HBlayout.addWidget(self.btnLoad)
HBlayout.addWidget(self.btnPixInfo)
VBlayout.addLayout(HBlayout)
def loadImage(self):
self.viewer.setPhoto(QtGui.QPixmap('pic.jpg'))
def pixInfo(self):
print 'Now in pixel info mode'
self.viewer._photo.mousePressEvent = self.viewer.printPixInfo
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 800, 600)
window.show()
sys.exit(app.exec_())
I would also like to know, once I have successfully changed the mouse click method, what the syntax is for returning the mouse left-click back to the default pan/drag behaviour.
I am a python novice, so try not to criticise me too much please. Thanks.
The problem is that by completely overriding mousePressEvent
you are losing all of the default behaviour (e.g. panning with mouse). It would be better to use a custom signal to notify when the photo has been clicked. And since the graphics-view already has an API for changing the drag-mode, you could add a slot for switching modes:
class PhotoViewer(QtGui.QGraphicsView):
photoClicked = QtCore.pyqtSignal(QtCore.QPoint)
...
def toggleDragMode(self):
if self.dragMode() == QtGui.QGraphicsView.ScrollHandDrag:
self.setDragMode(QtGui.QGraphicsView.NoDrag)
elif not self._photo.pixmap().isNull():
self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)
def mousePressEvent(self, event):
if self._photo.isUnderMouse():
self.photoClicked.emit(QtCore.QPoint(event.pos()))
# keep the default behaviour
super(PhotoViewer, self).mousePressEvent(event)
These two new APIs can then be used to give you the functionality you want:
class Window(QtGui.QWidget):
def __init__(self):
...
# add a line-edit to display the position info
self.editPixInfo = QtGui.QLineEdit(self)
self.editPixInfo.setReadOnly(True)
self.viewer.photoClicked.connect(self.photoClicked)
...
HBlayout.addWidget(self.editPixInfo)
def pixInfo(self):
# switch modes
self.viewer.toggleDragMode()
def photoClicked(self, pos):
# handle photo-click signals
if self.viewer.dragMode() == QtGui.QGraphicsView.NoDrag:
self.editPixInfo.setText('%d, %d' % (pos.x(), pos.y()))