butterfly_registrator.aux_viewing

QGraphicsView with synchronized pan and zoom functionality.

Not intended as a script.

Creates the base (main) view of the SplitView for the Butterfly Viewer and Registrator.

Credits:

PyQt MDI Image Viewer by tpgit (http://tpgit.github.io/MDIImageViewer/) for sync pan and zoom.

  1#!/usr/bin/env python3
  2
  3"""QGraphicsView with synchronized pan and zoom functionality.
  4
  5Not intended as a script.
  6
  7Creates the base (main) view of the SplitView for the Butterfly Viewer and Registrator.
  8
  9Credits:
 10    PyQt MDI Image Viewer by tpgit (http://tpgit.github.io/MDIImageViewer/) for sync pan and zoom.
 11"""
 12# SPDX-License-Identifier: GPL-3.0-or-later
 13
 14
 15
 16from PyQt5 import QtCore, QtGui, QtWidgets
 17
 18
 19
 20class SynchableGraphicsView(QtWidgets.QGraphicsView):
 21    """Extend QGraphicsView for synchronous panning and zooming between multiple instances.
 22
 23    Extends QGraphicsView:
 24        Pan and zoom signals.
 25        Scrolling operations and mouse wheel zooming.
 26        Mouse tracking.
 27        View shortcuts (for example, centering the view).
 28
 29    Args:
 30        scene (QGraphicsScene)
 31        parent (QWidget or child class thereof)
 32    """
 33
 34
 35    def __init__(self, scene=None, parent=None):
 36        if scene:
 37            super(SynchableGraphicsView, self).__init__(scene, parent)
 38        else:
 39            super(SynchableGraphicsView, self).__init__(parent)
 40        
 41        self.scroll_to_zoom_always_on = True
 42        
 43        self._handDrag = False
 44
 45        self.clearTransformChanges()
 46        self.connectSbarSignals(self.scrollChanged)
 47        
 48        self.setMouseTracking(True) # Allows split to follow the mouse cursor without needing to click the mouse (expected split behavior) 
 49        self.right_clicked_is_pressed = False
 50        
 51        self.installEventFilter(self)
 52    
 53
 54    # Signals
 55
 56    transformChanged = QtCore.pyqtSignal()
 57    """Transformed Changed **Signal**.
 58
 59    Emitted whenever the |QGraphicsView| Transform matrix has been
 60    changed."""
 61    
 62    scrollChanged = QtCore.pyqtSignal()
 63    """Scroll Changed **Signal**.
 64
 65    Emitted whenever the scrollbar position or range has changed."""
 66    
 67    wheelNotches = QtCore.pyqtSignal(float)
 68    """Wheel Notches **Signal** (*float*).
 69
 70    Emitted whenever the mouse wheel has been rolled. A wheelnotch is
 71    equal to wheel delta / 240"""
 72    
 73    def connectSbarSignals(self, slot):
 74        """Connect to scrollbar changed signals to synchronize panning.
 75
 76        :param slot: slot to connect scrollbar signals to."""
 77        sbar = self.horizontalScrollBar()
 78        sbar.valueChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
 79        #sbar.sliderMoved.connect(slot, type=QtCore.Qt.UniqueConnection)
 80        sbar.rangeChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
 81
 82        sbar = self.verticalScrollBar()
 83        sbar.valueChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
 84        #sbar.sliderMoved.connect(slot, type=QtCore.Qt.UniqueConnection)
 85        sbar.rangeChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
 86
 87        #self.scrollChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
 88
 89    def disconnectSbarSignals(self):
 90        """Disconnect from scrollbar changed signals."""
 91        sbar = self.horizontalScrollBar()
 92        sbar.valueChanged.disconnect()
 93        #sbar.sliderMoved.disconnect()
 94        sbar.rangeChanged.disconnect()
 95
 96        sbar = self.verticalScrollBar()
 97        sbar.valueChanged.disconnect()
 98        #sbar.sliderMoved.disconnect()
 99        sbar.rangeChanged.disconnect()
100    
101    right_mouse_button_was_clicked = QtCore.pyqtSignal(QtCore.QPoint)
102
103    # Properties
104
105    @property
106    def handDragging(self):
107        """Hand dragging state (*bool*)"""
108        return self._handDrag
109
110    @property
111    def scrollState(self):
112        """Tuple of percentage of scene extents
113        *(sceneWidthPercent, sceneHeightPercent)*"""
114        centerPoint = self.mapToScene(self.viewport().width()/2,
115                                      self.viewport().height()/2)
116        sceneRect = self.sceneRect()
117        centerWidth = centerPoint.x() - sceneRect.left()
118        centerHeight = centerPoint.y() - sceneRect.top()
119        sceneWidth =  sceneRect.width()
120        sceneHeight = sceneRect.height()
121
122        sceneWidthPercent = centerWidth / sceneWidth if sceneWidth != 0 else 0
123        sceneHeightPercent = centerHeight / sceneHeight if sceneHeight != 0 else 0
124        return (sceneWidthPercent, sceneHeightPercent)
125
126    @scrollState.setter
127    def scrollState(self, state):
128        sceneWidthPercent, sceneHeightPercent = state
129        x = (sceneWidthPercent * self.sceneRect().width() +
130             self.sceneRect().left())
131        y = (sceneHeightPercent * self.sceneRect().height() +
132             self.sceneRect().top())
133        self.centerOn(x, y)
134
135    @property
136    def zoomFactor(self):
137        """Zoom scale factor (*float*)."""
138        return self.transform().m11()
139
140    @zoomFactor.setter
141    def zoomFactor(self, newZoomFactor):
142        newZoomFactor = newZoomFactor / self.zoomFactor
143        self.scale(newZoomFactor, newZoomFactor)
144
145    
146    # Events
147
148    def mouseMoveEvent(self, event):
149        event.ignore()
150        return super().mouseMoveEvent(event)
151
152    def wheelEvent(self, wheelEvent):
153        """Overrides the wheelEvent to handle zooming.
154
155        :param QWheelEvent wheelEvent: instance of |QWheelEvent|"""
156        assert isinstance(wheelEvent, QtGui.QWheelEvent)
157        if (wheelEvent.modifiers() & QtCore.Qt.ControlModifier) or self.scroll_to_zoom_always_on:
158            self.wheelNotches.emit(wheelEvent.angleDelta().y() / 240.0)
159            wheelEvent.accept()
160        else:
161            super(SynchableGraphicsView, self).wheelEvent(wheelEvent)
162
163    def keyReleaseEvent(self, keyEvent):
164        """Overrides to make sure key release passed on to other classes.
165
166        :param QKeyEvent keyEvent: instance of |QKeyEvent|"""
167        assert isinstance(keyEvent, QtGui.QKeyEvent)
168        #print("graphicsView keyRelease count=%d, autoRepeat=%s" %
169              #(keyEvent.count(), keyEvent.isAutoRepeat()))
170        keyEvent.ignore()
171        #super(SynchableGraphicsView, self).keyReleaseEvent(keyEvent)
172
173    def mousePressEvent(self, event):
174        """Overrides to allow left-click for panning and limit repeat right-clicks.
175        
176        Enables hand dragging (panning) of the view by clicking left mouse button.
177        
178        Parameters:
179        
180        - event: [PtQt event]."""
181        assert isinstance(event, QtGui.QMouseEvent)
182        if event.button() == QtCore.Qt.LeftButton:  # Pan the image by clicking and dragging left mouse button
183            self.enableHandDrag(True)
184            event.accept()
185        if (not self.right_clicked_is_pressed) and (event.button() == QtCore.Qt.RightButton): # Indicate right button is pressed to prevent rapid repeating release signals 
186            self.right_clicked_is_pressed = True
187        super().mousePressEvent(event)
188
189    def mouseReleaseEvent(self, event):
190        """Overrides to disable panning left-click for panning and limit repeat right-clicks.
191        
192        Undoes the actions from mousePressEvent (release of mouse buttons).
193        
194        Parameters:
195        
196        - event: [PtQt event]."""
197        assert isinstance(event, QtGui.QMouseEvent)
198        if event.button() == QtCore.Qt.LeftButton:
199            self.enableHandDrag(False)
200            event.accept()
201        if (self.right_clicked_is_pressed) and (event.button() == QtCore.Qt.RightButton):
202            self.right_clicked_is_pressed = False
203            self.right_mouse_button_was_clicked.emit(event.pos())
204        super().mouseReleaseEvent(event)
205
206    def dragEnterEvent(self, event):
207        """Ignore drag event, thus passing it along to the parent widget.
208
209        Allows for the drag-and-drop function in digitaltwin_imageviewer.py by 
210        passing drag events to the underlying MDI area.
211        
212        Parameters:
213        
214        - event : [PyQt event]"""
215        event.ignore() # Give the drag event to the parent widget (e.g., to allow drag-and-drop in multi-instance viewers)
216    
217
218    # Zoom and pan (scrolling) methods
219
220    def checkTransformChanged(self):
221        """Return True if view transform has changed.
222
223        :rtype: bool"""
224        delta = 0.001
225        result = False
226
227        def different(t, u):
228            if u == 0.0:
229                d = abs(t - u)
230            else:
231                d = abs((t - u) / u)
232            return d > delta
233
234        t = self.transform()
235        u = self._transform
236
237        if False:
238            print("t = ")
239            self.dumpTransform(t, "    ")
240            print("u = ")
241            self.dumpTransform(u, "    ")
242            print("")
243
244        if (different(t.m11(), u.m11()) or
245            different(t.m22(), u.m22()) or
246            different(t.m12(), u.m12()) or
247            different(t.m21(), u.m21()) or
248            different(t.m31(), u.m31()) or
249            different(t.m32(), u.m32())):
250            self._transform = t
251            self.transformChanged.emit()
252            result = True
253        return result
254
255    def clearTransformChanges(self):
256        """Reset view transform changed info."""
257        self._transform = self.transform()
258
259    def scrollToTop(self):
260        """Scroll view to top."""
261        sbar = self.verticalScrollBar()
262        sbar.setValue(sbar.minimum())
263
264    def scrollToBottom(self):
265        """Scroll view to bottom."""
266        sbar = self.verticalScrollBar()
267        sbar.setValue(sbar.maximum())
268
269    def scrollToBegin(self):
270        """Scroll view to left edge."""
271        sbar = self.horizontalScrollBar()
272        sbar.setValue(sbar.minimum())
273
274    def scrollToEnd(self):
275        """Scroll view to right edge."""
276        sbar = self.horizontalScrollBar()
277        sbar.setValue(sbar.maximum())
278
279    def centerView(self):
280        """Center view."""
281        sbar = self.verticalScrollBar()
282        sbar.setValue((sbar.maximum() + sbar.minimum())/2)
283        sbar = self.horizontalScrollBar()
284        sbar.setValue((sbar.maximum() + sbar.minimum())/2)
285
286    def enableScrollBars(self, enable):
287        """Set visibility of the view's scrollbars.
288
289        :param bool enable: True to enable the scrollbars """
290        if enable:
291            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
292            self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
293        else:
294            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
295            self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
296
297    def enableHandDrag(self, enable):
298        """Set whether dragging the view with the hand cursor is allowed.
299
300        :param bool enable: True to enable hand dragging """
301        if enable:
302            if not self._handDrag:
303                self._handDrag = True
304                self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
305        else:
306            if self._handDrag:
307                self._handDrag = False
308                self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
309    
310
311    # Printing methods
312
313    def dumpTransform(self, t, padding=""):
314        """Dump the transform t to stdout.
315
316        :param t: the transform to dump
317        :param str padding: each line is preceded by padding"""
318        print("%s%5.3f %5.3f %5.3f" % (padding, t.m11(), t.m12(), t.m13()))
319        print("%s%5.3f %5.3f %5.3f" % (padding, t.m21(), t.m22(), t.m23()))
320        print("%s%5.3f %5.3f %5.3f" % (padding, t.m31(), t.m32(), t.m33()))
class SynchableGraphicsView(PyQt5.QtWidgets.QGraphicsView):
 21class SynchableGraphicsView(QtWidgets.QGraphicsView):
 22    """Extend QGraphicsView for synchronous panning and zooming between multiple instances.
 23
 24    Extends QGraphicsView:
 25        Pan and zoom signals.
 26        Scrolling operations and mouse wheel zooming.
 27        Mouse tracking.
 28        View shortcuts (for example, centering the view).
 29
 30    Args:
 31        scene (QGraphicsScene)
 32        parent (QWidget or child class thereof)
 33    """
 34
 35
 36    def __init__(self, scene=None, parent=None):
 37        if scene:
 38            super(SynchableGraphicsView, self).__init__(scene, parent)
 39        else:
 40            super(SynchableGraphicsView, self).__init__(parent)
 41        
 42        self.scroll_to_zoom_always_on = True
 43        
 44        self._handDrag = False
 45
 46        self.clearTransformChanges()
 47        self.connectSbarSignals(self.scrollChanged)
 48        
 49        self.setMouseTracking(True) # Allows split to follow the mouse cursor without needing to click the mouse (expected split behavior) 
 50        self.right_clicked_is_pressed = False
 51        
 52        self.installEventFilter(self)
 53    
 54
 55    # Signals
 56
 57    transformChanged = QtCore.pyqtSignal()
 58    """Transformed Changed **Signal**.
 59
 60    Emitted whenever the |QGraphicsView| Transform matrix has been
 61    changed."""
 62    
 63    scrollChanged = QtCore.pyqtSignal()
 64    """Scroll Changed **Signal**.
 65
 66    Emitted whenever the scrollbar position or range has changed."""
 67    
 68    wheelNotches = QtCore.pyqtSignal(float)
 69    """Wheel Notches **Signal** (*float*).
 70
 71    Emitted whenever the mouse wheel has been rolled. A wheelnotch is
 72    equal to wheel delta / 240"""
 73    
 74    def connectSbarSignals(self, slot):
 75        """Connect to scrollbar changed signals to synchronize panning.
 76
 77        :param slot: slot to connect scrollbar signals to."""
 78        sbar = self.horizontalScrollBar()
 79        sbar.valueChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
 80        #sbar.sliderMoved.connect(slot, type=QtCore.Qt.UniqueConnection)
 81        sbar.rangeChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
 82
 83        sbar = self.verticalScrollBar()
 84        sbar.valueChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
 85        #sbar.sliderMoved.connect(slot, type=QtCore.Qt.UniqueConnection)
 86        sbar.rangeChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
 87
 88        #self.scrollChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
 89
 90    def disconnectSbarSignals(self):
 91        """Disconnect from scrollbar changed signals."""
 92        sbar = self.horizontalScrollBar()
 93        sbar.valueChanged.disconnect()
 94        #sbar.sliderMoved.disconnect()
 95        sbar.rangeChanged.disconnect()
 96
 97        sbar = self.verticalScrollBar()
 98        sbar.valueChanged.disconnect()
 99        #sbar.sliderMoved.disconnect()
100        sbar.rangeChanged.disconnect()
101    
102    right_mouse_button_was_clicked = QtCore.pyqtSignal(QtCore.QPoint)
103
104    # Properties
105
106    @property
107    def handDragging(self):
108        """Hand dragging state (*bool*)"""
109        return self._handDrag
110
111    @property
112    def scrollState(self):
113        """Tuple of percentage of scene extents
114        *(sceneWidthPercent, sceneHeightPercent)*"""
115        centerPoint = self.mapToScene(self.viewport().width()/2,
116                                      self.viewport().height()/2)
117        sceneRect = self.sceneRect()
118        centerWidth = centerPoint.x() - sceneRect.left()
119        centerHeight = centerPoint.y() - sceneRect.top()
120        sceneWidth =  sceneRect.width()
121        sceneHeight = sceneRect.height()
122
123        sceneWidthPercent = centerWidth / sceneWidth if sceneWidth != 0 else 0
124        sceneHeightPercent = centerHeight / sceneHeight if sceneHeight != 0 else 0
125        return (sceneWidthPercent, sceneHeightPercent)
126
127    @scrollState.setter
128    def scrollState(self, state):
129        sceneWidthPercent, sceneHeightPercent = state
130        x = (sceneWidthPercent * self.sceneRect().width() +
131             self.sceneRect().left())
132        y = (sceneHeightPercent * self.sceneRect().height() +
133             self.sceneRect().top())
134        self.centerOn(x, y)
135
136    @property
137    def zoomFactor(self):
138        """Zoom scale factor (*float*)."""
139        return self.transform().m11()
140
141    @zoomFactor.setter
142    def zoomFactor(self, newZoomFactor):
143        newZoomFactor = newZoomFactor / self.zoomFactor
144        self.scale(newZoomFactor, newZoomFactor)
145
146    
147    # Events
148
149    def mouseMoveEvent(self, event):
150        event.ignore()
151        return super().mouseMoveEvent(event)
152
153    def wheelEvent(self, wheelEvent):
154        """Overrides the wheelEvent to handle zooming.
155
156        :param QWheelEvent wheelEvent: instance of |QWheelEvent|"""
157        assert isinstance(wheelEvent, QtGui.QWheelEvent)
158        if (wheelEvent.modifiers() & QtCore.Qt.ControlModifier) or self.scroll_to_zoom_always_on:
159            self.wheelNotches.emit(wheelEvent.angleDelta().y() / 240.0)
160            wheelEvent.accept()
161        else:
162            super(SynchableGraphicsView, self).wheelEvent(wheelEvent)
163
164    def keyReleaseEvent(self, keyEvent):
165        """Overrides to make sure key release passed on to other classes.
166
167        :param QKeyEvent keyEvent: instance of |QKeyEvent|"""
168        assert isinstance(keyEvent, QtGui.QKeyEvent)
169        #print("graphicsView keyRelease count=%d, autoRepeat=%s" %
170              #(keyEvent.count(), keyEvent.isAutoRepeat()))
171        keyEvent.ignore()
172        #super(SynchableGraphicsView, self).keyReleaseEvent(keyEvent)
173
174    def mousePressEvent(self, event):
175        """Overrides to allow left-click for panning and limit repeat right-clicks.
176        
177        Enables hand dragging (panning) of the view by clicking left mouse button.
178        
179        Parameters:
180        
181        - event: [PtQt event]."""
182        assert isinstance(event, QtGui.QMouseEvent)
183        if event.button() == QtCore.Qt.LeftButton:  # Pan the image by clicking and dragging left mouse button
184            self.enableHandDrag(True)
185            event.accept()
186        if (not self.right_clicked_is_pressed) and (event.button() == QtCore.Qt.RightButton): # Indicate right button is pressed to prevent rapid repeating release signals 
187            self.right_clicked_is_pressed = True
188        super().mousePressEvent(event)
189
190    def mouseReleaseEvent(self, event):
191        """Overrides to disable panning left-click for panning and limit repeat right-clicks.
192        
193        Undoes the actions from mousePressEvent (release of mouse buttons).
194        
195        Parameters:
196        
197        - event: [PtQt event]."""
198        assert isinstance(event, QtGui.QMouseEvent)
199        if event.button() == QtCore.Qt.LeftButton:
200            self.enableHandDrag(False)
201            event.accept()
202        if (self.right_clicked_is_pressed) and (event.button() == QtCore.Qt.RightButton):
203            self.right_clicked_is_pressed = False
204            self.right_mouse_button_was_clicked.emit(event.pos())
205        super().mouseReleaseEvent(event)
206
207    def dragEnterEvent(self, event):
208        """Ignore drag event, thus passing it along to the parent widget.
209
210        Allows for the drag-and-drop function in digitaltwin_imageviewer.py by 
211        passing drag events to the underlying MDI area.
212        
213        Parameters:
214        
215        - event : [PyQt event]"""
216        event.ignore() # Give the drag event to the parent widget (e.g., to allow drag-and-drop in multi-instance viewers)
217    
218
219    # Zoom and pan (scrolling) methods
220
221    def checkTransformChanged(self):
222        """Return True if view transform has changed.
223
224        :rtype: bool"""
225        delta = 0.001
226        result = False
227
228        def different(t, u):
229            if u == 0.0:
230                d = abs(t - u)
231            else:
232                d = abs((t - u) / u)
233            return d > delta
234
235        t = self.transform()
236        u = self._transform
237
238        if False:
239            print("t = ")
240            self.dumpTransform(t, "    ")
241            print("u = ")
242            self.dumpTransform(u, "    ")
243            print("")
244
245        if (different(t.m11(), u.m11()) or
246            different(t.m22(), u.m22()) or
247            different(t.m12(), u.m12()) or
248            different(t.m21(), u.m21()) or
249            different(t.m31(), u.m31()) or
250            different(t.m32(), u.m32())):
251            self._transform = t
252            self.transformChanged.emit()
253            result = True
254        return result
255
256    def clearTransformChanges(self):
257        """Reset view transform changed info."""
258        self._transform = self.transform()
259
260    def scrollToTop(self):
261        """Scroll view to top."""
262        sbar = self.verticalScrollBar()
263        sbar.setValue(sbar.minimum())
264
265    def scrollToBottom(self):
266        """Scroll view to bottom."""
267        sbar = self.verticalScrollBar()
268        sbar.setValue(sbar.maximum())
269
270    def scrollToBegin(self):
271        """Scroll view to left edge."""
272        sbar = self.horizontalScrollBar()
273        sbar.setValue(sbar.minimum())
274
275    def scrollToEnd(self):
276        """Scroll view to right edge."""
277        sbar = self.horizontalScrollBar()
278        sbar.setValue(sbar.maximum())
279
280    def centerView(self):
281        """Center view."""
282        sbar = self.verticalScrollBar()
283        sbar.setValue((sbar.maximum() + sbar.minimum())/2)
284        sbar = self.horizontalScrollBar()
285        sbar.setValue((sbar.maximum() + sbar.minimum())/2)
286
287    def enableScrollBars(self, enable):
288        """Set visibility of the view's scrollbars.
289
290        :param bool enable: True to enable the scrollbars """
291        if enable:
292            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
293            self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
294        else:
295            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
296            self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
297
298    def enableHandDrag(self, enable):
299        """Set whether dragging the view with the hand cursor is allowed.
300
301        :param bool enable: True to enable hand dragging """
302        if enable:
303            if not self._handDrag:
304                self._handDrag = True
305                self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
306        else:
307            if self._handDrag:
308                self._handDrag = False
309                self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
310    
311
312    # Printing methods
313
314    def dumpTransform(self, t, padding=""):
315        """Dump the transform t to stdout.
316
317        :param t: the transform to dump
318        :param str padding: each line is preceded by padding"""
319        print("%s%5.3f %5.3f %5.3f" % (padding, t.m11(), t.m12(), t.m13()))
320        print("%s%5.3f %5.3f %5.3f" % (padding, t.m21(), t.m22(), t.m23()))
321        print("%s%5.3f %5.3f %5.3f" % (padding, t.m31(), t.m32(), t.m33()))

Extend QGraphicsView for synchronous panning and zooming between multiple instances.

Extends QGraphicsView:

Pan and zoom signals. Scrolling operations and mouse wheel zooming. Mouse tracking. View shortcuts (for example, centering the view).

Arguments:
  • scene (QGraphicsScene)
  • parent (QWidget or child class thereof)
def transformChanged(unknown):

Transformed Changed Signal.

Emitted whenever the |QGraphicsView| Transform matrix has been changed.

def scrollChanged(unknown):

Scroll Changed Signal.

Emitted whenever the scrollbar position or range has changed.

def wheelNotches(unknown):

Wheel Notches Signal (float).

Emitted whenever the mouse wheel has been rolled. A wheelnotch is equal to wheel delta / 240

def connectSbarSignals(self, slot):
74    def connectSbarSignals(self, slot):
75        """Connect to scrollbar changed signals to synchronize panning.
76
77        :param slot: slot to connect scrollbar signals to."""
78        sbar = self.horizontalScrollBar()
79        sbar.valueChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
80        #sbar.sliderMoved.connect(slot, type=QtCore.Qt.UniqueConnection)
81        sbar.rangeChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
82
83        sbar = self.verticalScrollBar()
84        sbar.valueChanged.connect(slot, type=QtCore.Qt.UniqueConnection)
85        #sbar.sliderMoved.connect(slot, type=QtCore.Qt.UniqueConnection)
86        sbar.rangeChanged.connect(slot, type=QtCore.Qt.UniqueConnection)

Connect to scrollbar changed signals to synchronize panning.

Parameters
  • slot: slot to connect scrollbar signals to.
def disconnectSbarSignals(self):
 90    def disconnectSbarSignals(self):
 91        """Disconnect from scrollbar changed signals."""
 92        sbar = self.horizontalScrollBar()
 93        sbar.valueChanged.disconnect()
 94        #sbar.sliderMoved.disconnect()
 95        sbar.rangeChanged.disconnect()
 96
 97        sbar = self.verticalScrollBar()
 98        sbar.valueChanged.disconnect()
 99        #sbar.sliderMoved.disconnect()
100        sbar.rangeChanged.disconnect()

Disconnect from scrollbar changed signals.

handDragging

Hand dragging state (bool)

scrollState

Tuple of percentage of scene extents (sceneWidthPercent, sceneHeightPercent)

zoomFactor

Zoom scale factor (float).

def mouseMoveEvent(self, event):
149    def mouseMoveEvent(self, event):
150        event.ignore()
151        return super().mouseMoveEvent(event)

mouseMoveEvent(self, QMouseEvent)

def wheelEvent(self, wheelEvent):
153    def wheelEvent(self, wheelEvent):
154        """Overrides the wheelEvent to handle zooming.
155
156        :param QWheelEvent wheelEvent: instance of |QWheelEvent|"""
157        assert isinstance(wheelEvent, QtGui.QWheelEvent)
158        if (wheelEvent.modifiers() & QtCore.Qt.ControlModifier) or self.scroll_to_zoom_always_on:
159            self.wheelNotches.emit(wheelEvent.angleDelta().y() / 240.0)
160            wheelEvent.accept()
161        else:
162            super(SynchableGraphicsView, self).wheelEvent(wheelEvent)

Overrides the wheelEvent to handle zooming.

Parameters
  • QWheelEvent wheelEvent: instance of |QWheelEvent|
def keyReleaseEvent(self, keyEvent):
164    def keyReleaseEvent(self, keyEvent):
165        """Overrides to make sure key release passed on to other classes.
166
167        :param QKeyEvent keyEvent: instance of |QKeyEvent|"""
168        assert isinstance(keyEvent, QtGui.QKeyEvent)
169        #print("graphicsView keyRelease count=%d, autoRepeat=%s" %
170              #(keyEvent.count(), keyEvent.isAutoRepeat()))
171        keyEvent.ignore()

Overrides to make sure key release passed on to other classes.

Parameters
  • QKeyEvent keyEvent: instance of |QKeyEvent|
def mousePressEvent(self, event):
174    def mousePressEvent(self, event):
175        """Overrides to allow left-click for panning and limit repeat right-clicks.
176        
177        Enables hand dragging (panning) of the view by clicking left mouse button.
178        
179        Parameters:
180        
181        - event: [PtQt event]."""
182        assert isinstance(event, QtGui.QMouseEvent)
183        if event.button() == QtCore.Qt.LeftButton:  # Pan the image by clicking and dragging left mouse button
184            self.enableHandDrag(True)
185            event.accept()
186        if (not self.right_clicked_is_pressed) and (event.button() == QtCore.Qt.RightButton): # Indicate right button is pressed to prevent rapid repeating release signals 
187            self.right_clicked_is_pressed = True
188        super().mousePressEvent(event)

Overrides to allow left-click for panning and limit repeat right-clicks.

Enables hand dragging (panning) of the view by clicking left mouse button.

Parameters:

  • event: [PtQt event].
def mouseReleaseEvent(self, event):
190    def mouseReleaseEvent(self, event):
191        """Overrides to disable panning left-click for panning and limit repeat right-clicks.
192        
193        Undoes the actions from mousePressEvent (release of mouse buttons).
194        
195        Parameters:
196        
197        - event: [PtQt event]."""
198        assert isinstance(event, QtGui.QMouseEvent)
199        if event.button() == QtCore.Qt.LeftButton:
200            self.enableHandDrag(False)
201            event.accept()
202        if (self.right_clicked_is_pressed) and (event.button() == QtCore.Qt.RightButton):
203            self.right_clicked_is_pressed = False
204            self.right_mouse_button_was_clicked.emit(event.pos())
205        super().mouseReleaseEvent(event)

Overrides to disable panning left-click for panning and limit repeat right-clicks.

Undoes the actions from mousePressEvent (release of mouse buttons).

Parameters:

  • event: [PtQt event].
def dragEnterEvent(self, event):
207    def dragEnterEvent(self, event):
208        """Ignore drag event, thus passing it along to the parent widget.
209
210        Allows for the drag-and-drop function in digitaltwin_imageviewer.py by 
211        passing drag events to the underlying MDI area.
212        
213        Parameters:
214        
215        - event : [PyQt event]"""
216        event.ignore() # Give the drag event to the parent widget (e.g., to allow drag-and-drop in multi-instance viewers)

Ignore drag event, thus passing it along to the parent widget.

Allows for the drag-and-drop function in digitaltwin_imageviewer.py by passing drag events to the underlying MDI area.

Parameters:

  • event : [PyQt event]
def checkTransformChanged(self):
221    def checkTransformChanged(self):
222        """Return True if view transform has changed.
223
224        :rtype: bool"""
225        delta = 0.001
226        result = False
227
228        def different(t, u):
229            if u == 0.0:
230                d = abs(t - u)
231            else:
232                d = abs((t - u) / u)
233            return d > delta
234
235        t = self.transform()
236        u = self._transform
237
238        if False:
239            print("t = ")
240            self.dumpTransform(t, "    ")
241            print("u = ")
242            self.dumpTransform(u, "    ")
243            print("")
244
245        if (different(t.m11(), u.m11()) or
246            different(t.m22(), u.m22()) or
247            different(t.m12(), u.m12()) or
248            different(t.m21(), u.m21()) or
249            different(t.m31(), u.m31()) or
250            different(t.m32(), u.m32())):
251            self._transform = t
252            self.transformChanged.emit()
253            result = True
254        return result

Return True if view transform has changed.

def clearTransformChanges(self):
256    def clearTransformChanges(self):
257        """Reset view transform changed info."""
258        self._transform = self.transform()

Reset view transform changed info.

def scrollToTop(self):
260    def scrollToTop(self):
261        """Scroll view to top."""
262        sbar = self.verticalScrollBar()
263        sbar.setValue(sbar.minimum())

Scroll view to top.

def scrollToBottom(self):
265    def scrollToBottom(self):
266        """Scroll view to bottom."""
267        sbar = self.verticalScrollBar()
268        sbar.setValue(sbar.maximum())

Scroll view to bottom.

def scrollToBegin(self):
270    def scrollToBegin(self):
271        """Scroll view to left edge."""
272        sbar = self.horizontalScrollBar()
273        sbar.setValue(sbar.minimum())

Scroll view to left edge.

def scrollToEnd(self):
275    def scrollToEnd(self):
276        """Scroll view to right edge."""
277        sbar = self.horizontalScrollBar()
278        sbar.setValue(sbar.maximum())

Scroll view to right edge.

def centerView(self):
280    def centerView(self):
281        """Center view."""
282        sbar = self.verticalScrollBar()
283        sbar.setValue((sbar.maximum() + sbar.minimum())/2)
284        sbar = self.horizontalScrollBar()
285        sbar.setValue((sbar.maximum() + sbar.minimum())/2)

Center view.

def enableScrollBars(self, enable):
287    def enableScrollBars(self, enable):
288        """Set visibility of the view's scrollbars.
289
290        :param bool enable: True to enable the scrollbars """
291        if enable:
292            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
293            self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
294        else:
295            self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
296            self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

Set visibility of the view's scrollbars.

Parameters
  • bool enable: True to enable the scrollbars
def enableHandDrag(self, enable):
298    def enableHandDrag(self, enable):
299        """Set whether dragging the view with the hand cursor is allowed.
300
301        :param bool enable: True to enable hand dragging """
302        if enable:
303            if not self._handDrag:
304                self._handDrag = True
305                self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
306        else:
307            if self._handDrag:
308                self._handDrag = False
309                self.setDragMode(QtWidgets.QGraphicsView.NoDrag)

Set whether dragging the view with the hand cursor is allowed.

Parameters
  • bool enable: True to enable hand dragging
def dumpTransform(self, t, padding=''):
314    def dumpTransform(self, t, padding=""):
315        """Dump the transform t to stdout.
316
317        :param t: the transform to dump
318        :param str padding: each line is preceded by padding"""
319        print("%s%5.3f %5.3f %5.3f" % (padding, t.m11(), t.m12(), t.m13()))
320        print("%s%5.3f %5.3f %5.3f" % (padding, t.m21(), t.m22(), t.m23()))
321        print("%s%5.3f %5.3f %5.3f" % (padding, t.m31(), t.m32(), t.m33()))

Dump the transform t to stdout.

Parameters
  • t: the transform to dump
  • str padding: each line is preceded by padding