Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. In a QGraphicsview, how can I keep the image moving when the cursor is on the edge, without having to move the mouse?
Forum Updated to NodeBB v4.3 + New Features

In a QGraphicsview, how can I keep the image moving when the cursor is on the edge, without having to move the mouse?

Scheduled Pinned Locked Moved Solved Qt for Python
8 Posts 4 Posters 1.6k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • E Offline
    E Offline
    elver
    wrote on last edited by
    #1

    In my program, I have a QDialog that displays a picture that you can zoom and pan around, if you pan with the right mouse button clicked, you can draw a rectangle. When you are drawing a rectangle and reach the edge of the view, the picture should move so that you can keep drawing the rectangle. Right now it only works if you keep moving the cursor slightly to trigger the mouseMoveEvent.

    If I try to programatically call the mouseMoveEvent when the mouse isnt moving, to keep the picture moving, it moves the picture, but for some reason the view doesnt update, so you only see that the picture has moved after moving the mouse again. So basically if you keep the mouse on the edge for some time without moving it, and then move it, the picture moves by a lot suddenly.

    Interestingly, when I call the mouseMoveEvent programatically, for some reason the event.position() changes, even though the event is always the same one.

    Here is a minimal reproducible example:

    the adjust_view function is the original one where you have to move the cursor up and down to keep the picture moving

    the adjust_view_loop function is my attempt to programatically make it work

    Im using Python 3.10.7 and PySide6.3.2

    from PySide6.QtCore import Signal, Qt, QRectF, QPoint, QRect
    from PySide6.QtGui import QPainter, QPixmap, QImage, QPen, QCursor, QMouseEvent
    from PySide6.QtWidgets import (
        QGraphicsView,
        QGraphicsScene,
        QGraphicsPixmapItem,
        QGraphicsRectItem,
        QApplication,
        QDialog,
        QDialogButtonBox,
        QVBoxLayout,
        QHBoxLayout,
    )
    
    import warnings
    import time
    import sys
    
    
    class MyDialog(QDialog):
        MIN_WIDTH = 900
        MIN_HEIGHT = int(MIN_WIDTH * 2 / 3)
    
        def __init__(self, path, parent=None):
            super().__init__(parent)
            self.path = path
            self.setFixedSize(self.MIN_WIDTH, self.MIN_HEIGHT)
    
            self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
            self.buttonBox.rejected.connect(self.reject)
            self.buttonBox.accepted.connect(self.accept)
    
            self.layout = QVBoxLayout()
            self.image_viewer = PhotoViewer(self)
            image = QImage(path)
            self.pixmap = QPixmap(image)
            self.image_viewer.set_photo(self.pixmap)
    
            bottom_layout = QHBoxLayout()
            bottom_layout.addWidget(self.buttonBox)
    
            self.layout.addWidget(self.image_viewer)
            self.layout.addLayout(bottom_layout)
            self.setLayout(self.layout)
    
        def exec(self):
            self.image_viewer.viewport().setMinimumSize(
                self.MIN_WIDTH - 100, self.MIN_HEIGHT - 100
            )
            self.image_viewer.fit_in_view()
            self.image_viewer.calc_delta()
            super().exec()
    
    
    class PhotoViewer(QGraphicsView):
        """
        class for displaying the pictures
        """
    
        DELTA = 10
        mouse_event_signal = Signal()
    
        def __init__(self, parent):
            super(PhotoViewer, self).__init__(parent)
            warnings.filterwarnings("error")
            self.zoom = 0
            self._empty = True
            self._photo = QGraphicsPixmapItem()
            self._photo.setTransformationMode(Qt.SmoothTransformation)
    
            self.set_scene()
            self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
            self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
            self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
            self.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.Antialiasing)
    
        def set_scene(self):
            self.scene = SelectColorBarDialogScene(self)
            self.scene.addItem(self._photo)
            self.setScene(self.scene)
    
        def fit_in_view(self, scale=True):
            """
            Resizes the photo to fit in the view
            """
            rect = QRectF(self._photo.pixmap().rect())
            if not rect.isNull():
                self.setSceneRect(rect)
                unity = self.transform().mapRect(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.zoom = 0
    
        def mousePressEvent(self, event):
            if event.buttons() & Qt.RightButton:
                self.update()
            super(PhotoViewer, self).mousePressEvent(event)
    
        def mouseMoveEvent(self, event, pass_adjust=False):
            super(PhotoViewer, self).mouseMoveEvent(event)
            if event.buttons() & Qt.RightButton:
                if not self.scene.begin.isNull() and not self.scene.destination.isNull():
                    if not pass_adjust:
                        # self.adjust_view(event)
                        self.adjust_view_loop(event)
    
                    print(event.globalPosition())
                    print(event.position())
                    print("_______________")
    
                self.update()
    
        #! When programatically changed, the mouse pos changes even though cursor is not moved
        def adjust_view_loop(self, event):
            edges = self.check_if_point_on_edge(self.scene.destination)
    
            if (
                edges
                and self.scene.destination.x() < self.photo_size.width() - self.DELTA
                and self.scene.destination.y() < self.photo_size.height() - self.DELTA
                and self.scene.destination.x() > self.DELTA
                and self.scene.destination.y() > self.DELTA
            ):
                i = 0
                self.cursor_pos = QCursor.pos()
                while True:
                    i += 1
                    old_view = self.mapToScene(self.viewport().rect()).boundingRect()
                    new_view = [0, 0, 0, 0]
                    for _ in range(edges.bit_count()):
                        if edges & 1 == 1:
                            new_view[1] = -self.DELTA
                            new_view[3] = -self.DELTA
                        elif edges & 4 == 4:
                            new_view[1] = self.DELTA
                            new_view[3] = self.DELTA
                        elif edges & 2 == 2:
                            new_view[0] = -self.DELTA
                            new_view[2] = -self.DELTA
                        elif edges & 8 == 8:
                            new_view[0] = self.DELTA
                            new_view[2] = self.DELTA
                    new_view_rect = old_view.adjusted(
                        new_view[0], new_view[1], new_view[2], new_view[3]
                    )
                    self.fitInView(new_view_rect, Qt.KeepAspectRatio)
                    x = self.scene.destination.x()
                    y = self.scene.destination.y()
                    self.set_rect()
                    if i % 2 == 0:
                        new_event = QMouseEvent(
                            event.type(),
                            QPoint(event.position().x(), event.position().y() + 1),
                            QPoint(
                                event.globalPosition().x(), event.globalPosition().y() + 1
                            ),
                            event.button(),
                            event.buttons(),
                            event.modifiers(),
                        )
                    else:
                        new_event = QMouseEvent(
                            event.type(),
                            QPoint(event.position().x(), event.position().y() - 1),
                            QPoint(
                                event.globalPosition().x(), event.globalPosition().y() - 1
                            ),
                            event.button(),
                            event.buttons(),
                            event.modifiers(),
                            event.pointingDevice(),
                        )
                    self.mouseMoveEvent(new_event, True)
                    self.update()
                    print("Looping...")
                    time.sleep(0.5)
                    if not self.on_loop_signal():
                        break
    
            self.set_rect()
    
        def adjust_view(self, event):
            edges = self.check_if_point_on_edge(self.scene.destination)
    
            if (
                edges
                and self.scene.destination.x() < self.photo_size.width() - self.DELTA
                and self.scene.destination.y() < self.photo_size.height() - self.DELTA
                and self.scene.destination.x() > self.DELTA
                and self.scene.destination.y() > self.DELTA
            ):
                old_view = self.mapToScene(self.viewport().rect()).boundingRect()
                new_view = [0, 0, 0, 0]
                for _ in range(edges.bit_count()):
                    if edges & 1 == 1:
                        new_view[1] = -self.DELTA
                        new_view[3] = -self.DELTA
                    elif edges & 4 == 4:
                        new_view[1] = self.DELTA
                        new_view[3] = self.DELTA
                    elif edges & 2 == 2:
                        new_view[0] = -self.DELTA
                        new_view[2] = -self.DELTA
                    elif edges & 8 == 8:
                        new_view[0] = self.DELTA
                        new_view[2] = self.DELTA
                new_view_rect = old_view.adjusted(
                    new_view[0], new_view[1], new_view[2], new_view[3]
                )
                self.fitInView(new_view_rect, Qt.KeepAspectRatio)
                x = self.scene.destination.x()
                y = self.scene.destination.y()
                self.set_rect()
    
            self.set_rect()
    
        def set_rect(self):
            for item in self.scene.items():
                if isinstance(item, QGraphicsRectItem):
                    self.scene.removeItem(item)
            self.rect = QRect(self.scene.begin, self.scene.destination)
            self.scene.addRect(self.rect.normalized(), QPen(Qt.red))
    
        def get_rect(self):
            return self.rect.normalized()
    
        def mouseReleaseEvent(self, event):
            if event.button() & Qt.RightButton:
                self.set_rect()
                self.update()
            super(PhotoViewer, self).mouseReleaseEvent(event)
    
        def check_if_point_on_edge(self, point):
            rect = self.mapToScene(
                self.viewport().rect()
            ).boundingRect()  # left, top, width, height
            edges = 0  # top = 1, bottom = 2, left = 4, right = 8
            internal_rect = rect.adjusted(self.DELTA, self.DELTA, -self.DELTA, -self.DELTA)
            if rect.contains(point) and not internal_rect.contains(point):
                if point.x() < internal_rect.left():
                    edges = edges | 2
                elif point.x() > internal_rect.right():
                    edges = edges | 8
                if point.y() < internal_rect.top():
                    edges = edges | 1
                elif point.y() > internal_rect.bottom():
                    edges = edges | 4
    
            return edges
    
        def wheelEvent(self, event):
            self.calc_delta()
            if event.angleDelta().y() > 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.fit_in_view()
            else:
                self.zoom = 0
    
        def calc_delta(self):
            rect_width = self.mapToScene(self.viewport().rect()).boundingRect().width()
            self.DELTA = int(rect_width / 25)
    
        def set_photo(self, pixmap=None):
            """
            Sets the photo to be displayed, if pixmap is None, the photo is set to empty
            """
            if pixmap and not pixmap.isNull():
                self._empty = False
                self.setDragMode(QGraphicsView.ScrollHandDrag)
                self._photo.setPixmap(pixmap)
            else:
                self._empty = True
                self.setDragMode(QGraphicsView.NoDrag)
                self._photo.setPixmap(QPixmap())
            self.photo_size = QRectF(self._photo.pixmap().rect())
    
        def on_loop_signal(self):
            if self.cursor_pos != QCursor.pos():
                self.mouse_event_signal.emit()
                return False
            return True
    
    
    class SelectColorBarDialogScene(QGraphicsScene):
        def __init__(self, parent):
            super(SelectColorBarDialogScene, self).__init__(parent)
            self.begin, self.destination = QPoint(), QPoint()
    
        def mousePressEvent(self, event):
            if event.buttons() & Qt.RightButton:
                self.begin = event.scenePos().toPoint()
                self.destination = self.begin
                self.update()
            super(SelectColorBarDialogScene, self).mousePressEvent(event)
    
        def mouseMoveEvent(self, event):
            if event.buttons() & Qt.RightButton:
                self.destination = event.scenePos().toPoint()
                self.update()
            super(SelectColorBarDialogScene, self).mouseMoveEvent(event)
    
        def mouseReleaseEvent(self, event):
            if event.button() & Qt.RightButton:
                self.begin, self.destination = QPoint(), QPoint()
                self.update()
            super(SelectColorBarDialogScene, self).mouseReleaseEvent(event)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        dialog = MyDialog(path=None)  #! set picture path here
        dialog.exec()
        sys.exit(app.exec_())
    
    JonBJ jeremy_kJ 2 Replies Last reply
    0
    • E elver

      In my program, I have a QDialog that displays a picture that you can zoom and pan around, if you pan with the right mouse button clicked, you can draw a rectangle. When you are drawing a rectangle and reach the edge of the view, the picture should move so that you can keep drawing the rectangle. Right now it only works if you keep moving the cursor slightly to trigger the mouseMoveEvent.

      If I try to programatically call the mouseMoveEvent when the mouse isnt moving, to keep the picture moving, it moves the picture, but for some reason the view doesnt update, so you only see that the picture has moved after moving the mouse again. So basically if you keep the mouse on the edge for some time without moving it, and then move it, the picture moves by a lot suddenly.

      Interestingly, when I call the mouseMoveEvent programatically, for some reason the event.position() changes, even though the event is always the same one.

      Here is a minimal reproducible example:

      the adjust_view function is the original one where you have to move the cursor up and down to keep the picture moving

      the adjust_view_loop function is my attempt to programatically make it work

      Im using Python 3.10.7 and PySide6.3.2

      from PySide6.QtCore import Signal, Qt, QRectF, QPoint, QRect
      from PySide6.QtGui import QPainter, QPixmap, QImage, QPen, QCursor, QMouseEvent
      from PySide6.QtWidgets import (
          QGraphicsView,
          QGraphicsScene,
          QGraphicsPixmapItem,
          QGraphicsRectItem,
          QApplication,
          QDialog,
          QDialogButtonBox,
          QVBoxLayout,
          QHBoxLayout,
      )
      
      import warnings
      import time
      import sys
      
      
      class MyDialog(QDialog):
          MIN_WIDTH = 900
          MIN_HEIGHT = int(MIN_WIDTH * 2 / 3)
      
          def __init__(self, path, parent=None):
              super().__init__(parent)
              self.path = path
              self.setFixedSize(self.MIN_WIDTH, self.MIN_HEIGHT)
      
              self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
              self.buttonBox.rejected.connect(self.reject)
              self.buttonBox.accepted.connect(self.accept)
      
              self.layout = QVBoxLayout()
              self.image_viewer = PhotoViewer(self)
              image = QImage(path)
              self.pixmap = QPixmap(image)
              self.image_viewer.set_photo(self.pixmap)
      
              bottom_layout = QHBoxLayout()
              bottom_layout.addWidget(self.buttonBox)
      
              self.layout.addWidget(self.image_viewer)
              self.layout.addLayout(bottom_layout)
              self.setLayout(self.layout)
      
          def exec(self):
              self.image_viewer.viewport().setMinimumSize(
                  self.MIN_WIDTH - 100, self.MIN_HEIGHT - 100
              )
              self.image_viewer.fit_in_view()
              self.image_viewer.calc_delta()
              super().exec()
      
      
      class PhotoViewer(QGraphicsView):
          """
          class for displaying the pictures
          """
      
          DELTA = 10
          mouse_event_signal = Signal()
      
          def __init__(self, parent):
              super(PhotoViewer, self).__init__(parent)
              warnings.filterwarnings("error")
              self.zoom = 0
              self._empty = True
              self._photo = QGraphicsPixmapItem()
              self._photo.setTransformationMode(Qt.SmoothTransformation)
      
              self.set_scene()
              self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
              self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
              self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
              self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
              self.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.Antialiasing)
      
          def set_scene(self):
              self.scene = SelectColorBarDialogScene(self)
              self.scene.addItem(self._photo)
              self.setScene(self.scene)
      
          def fit_in_view(self, scale=True):
              """
              Resizes the photo to fit in the view
              """
              rect = QRectF(self._photo.pixmap().rect())
              if not rect.isNull():
                  self.setSceneRect(rect)
                  unity = self.transform().mapRect(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.zoom = 0
      
          def mousePressEvent(self, event):
              if event.buttons() & Qt.RightButton:
                  self.update()
              super(PhotoViewer, self).mousePressEvent(event)
      
          def mouseMoveEvent(self, event, pass_adjust=False):
              super(PhotoViewer, self).mouseMoveEvent(event)
              if event.buttons() & Qt.RightButton:
                  if not self.scene.begin.isNull() and not self.scene.destination.isNull():
                      if not pass_adjust:
                          # self.adjust_view(event)
                          self.adjust_view_loop(event)
      
                      print(event.globalPosition())
                      print(event.position())
                      print("_______________")
      
                  self.update()
      
          #! When programatically changed, the mouse pos changes even though cursor is not moved
          def adjust_view_loop(self, event):
              edges = self.check_if_point_on_edge(self.scene.destination)
      
              if (
                  edges
                  and self.scene.destination.x() < self.photo_size.width() - self.DELTA
                  and self.scene.destination.y() < self.photo_size.height() - self.DELTA
                  and self.scene.destination.x() > self.DELTA
                  and self.scene.destination.y() > self.DELTA
              ):
                  i = 0
                  self.cursor_pos = QCursor.pos()
                  while True:
                      i += 1
                      old_view = self.mapToScene(self.viewport().rect()).boundingRect()
                      new_view = [0, 0, 0, 0]
                      for _ in range(edges.bit_count()):
                          if edges & 1 == 1:
                              new_view[1] = -self.DELTA
                              new_view[3] = -self.DELTA
                          elif edges & 4 == 4:
                              new_view[1] = self.DELTA
                              new_view[3] = self.DELTA
                          elif edges & 2 == 2:
                              new_view[0] = -self.DELTA
                              new_view[2] = -self.DELTA
                          elif edges & 8 == 8:
                              new_view[0] = self.DELTA
                              new_view[2] = self.DELTA
                      new_view_rect = old_view.adjusted(
                          new_view[0], new_view[1], new_view[2], new_view[3]
                      )
                      self.fitInView(new_view_rect, Qt.KeepAspectRatio)
                      x = self.scene.destination.x()
                      y = self.scene.destination.y()
                      self.set_rect()
                      if i % 2 == 0:
                          new_event = QMouseEvent(
                              event.type(),
                              QPoint(event.position().x(), event.position().y() + 1),
                              QPoint(
                                  event.globalPosition().x(), event.globalPosition().y() + 1
                              ),
                              event.button(),
                              event.buttons(),
                              event.modifiers(),
                          )
                      else:
                          new_event = QMouseEvent(
                              event.type(),
                              QPoint(event.position().x(), event.position().y() - 1),
                              QPoint(
                                  event.globalPosition().x(), event.globalPosition().y() - 1
                              ),
                              event.button(),
                              event.buttons(),
                              event.modifiers(),
                              event.pointingDevice(),
                          )
                      self.mouseMoveEvent(new_event, True)
                      self.update()
                      print("Looping...")
                      time.sleep(0.5)
                      if not self.on_loop_signal():
                          break
      
              self.set_rect()
      
          def adjust_view(self, event):
              edges = self.check_if_point_on_edge(self.scene.destination)
      
              if (
                  edges
                  and self.scene.destination.x() < self.photo_size.width() - self.DELTA
                  and self.scene.destination.y() < self.photo_size.height() - self.DELTA
                  and self.scene.destination.x() > self.DELTA
                  and self.scene.destination.y() > self.DELTA
              ):
                  old_view = self.mapToScene(self.viewport().rect()).boundingRect()
                  new_view = [0, 0, 0, 0]
                  for _ in range(edges.bit_count()):
                      if edges & 1 == 1:
                          new_view[1] = -self.DELTA
                          new_view[3] = -self.DELTA
                      elif edges & 4 == 4:
                          new_view[1] = self.DELTA
                          new_view[3] = self.DELTA
                      elif edges & 2 == 2:
                          new_view[0] = -self.DELTA
                          new_view[2] = -self.DELTA
                      elif edges & 8 == 8:
                          new_view[0] = self.DELTA
                          new_view[2] = self.DELTA
                  new_view_rect = old_view.adjusted(
                      new_view[0], new_view[1], new_view[2], new_view[3]
                  )
                  self.fitInView(new_view_rect, Qt.KeepAspectRatio)
                  x = self.scene.destination.x()
                  y = self.scene.destination.y()
                  self.set_rect()
      
              self.set_rect()
      
          def set_rect(self):
              for item in self.scene.items():
                  if isinstance(item, QGraphicsRectItem):
                      self.scene.removeItem(item)
              self.rect = QRect(self.scene.begin, self.scene.destination)
              self.scene.addRect(self.rect.normalized(), QPen(Qt.red))
      
          def get_rect(self):
              return self.rect.normalized()
      
          def mouseReleaseEvent(self, event):
              if event.button() & Qt.RightButton:
                  self.set_rect()
                  self.update()
              super(PhotoViewer, self).mouseReleaseEvent(event)
      
          def check_if_point_on_edge(self, point):
              rect = self.mapToScene(
                  self.viewport().rect()
              ).boundingRect()  # left, top, width, height
              edges = 0  # top = 1, bottom = 2, left = 4, right = 8
              internal_rect = rect.adjusted(self.DELTA, self.DELTA, -self.DELTA, -self.DELTA)
              if rect.contains(point) and not internal_rect.contains(point):
                  if point.x() < internal_rect.left():
                      edges = edges | 2
                  elif point.x() > internal_rect.right():
                      edges = edges | 8
                  if point.y() < internal_rect.top():
                      edges = edges | 1
                  elif point.y() > internal_rect.bottom():
                      edges = edges | 4
      
              return edges
      
          def wheelEvent(self, event):
              self.calc_delta()
              if event.angleDelta().y() > 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.fit_in_view()
              else:
                  self.zoom = 0
      
          def calc_delta(self):
              rect_width = self.mapToScene(self.viewport().rect()).boundingRect().width()
              self.DELTA = int(rect_width / 25)
      
          def set_photo(self, pixmap=None):
              """
              Sets the photo to be displayed, if pixmap is None, the photo is set to empty
              """
              if pixmap and not pixmap.isNull():
                  self._empty = False
                  self.setDragMode(QGraphicsView.ScrollHandDrag)
                  self._photo.setPixmap(pixmap)
              else:
                  self._empty = True
                  self.setDragMode(QGraphicsView.NoDrag)
                  self._photo.setPixmap(QPixmap())
              self.photo_size = QRectF(self._photo.pixmap().rect())
      
          def on_loop_signal(self):
              if self.cursor_pos != QCursor.pos():
                  self.mouse_event_signal.emit()
                  return False
              return True
      
      
      class SelectColorBarDialogScene(QGraphicsScene):
          def __init__(self, parent):
              super(SelectColorBarDialogScene, self).__init__(parent)
              self.begin, self.destination = QPoint(), QPoint()
      
          def mousePressEvent(self, event):
              if event.buttons() & Qt.RightButton:
                  self.begin = event.scenePos().toPoint()
                  self.destination = self.begin
                  self.update()
              super(SelectColorBarDialogScene, self).mousePressEvent(event)
      
          def mouseMoveEvent(self, event):
              if event.buttons() & Qt.RightButton:
                  self.destination = event.scenePos().toPoint()
                  self.update()
              super(SelectColorBarDialogScene, self).mouseMoveEvent(event)
      
          def mouseReleaseEvent(self, event):
              if event.button() & Qt.RightButton:
                  self.begin, self.destination = QPoint(), QPoint()
                  self.update()
              super(SelectColorBarDialogScene, self).mouseReleaseEvent(event)
      
      
      if __name__ == "__main__":
          app = QApplication(sys.argv)
          dialog = MyDialog(path=None)  #! set picture path here
          dialog.exec()
          sys.exit(app.exec_())
      
      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by
      #2

      @elver
      Try the following.

      I would not "synthesize" a mouse (move) event in code (i.e. create a QMouseEvent and call self.mouseMoveEvent()). Factor the code out from your mouseMoveEvent() to do its work given suitable parameters. Call that from a genuine mouseMoveEvent() and also call it explicitly from your code when you want to do edge scroll.

      Don't do any time.sleep(), bad idea. Anything time-based needed should use QTimer to do the stuff after the sleep() instead.

      Get those done, then see whether you still have the "view doesnt update" issue.

      E 1 Reply Last reply
      1
      • JonBJ JonB

        @elver
        Try the following.

        I would not "synthesize" a mouse (move) event in code (i.e. create a QMouseEvent and call self.mouseMoveEvent()). Factor the code out from your mouseMoveEvent() to do its work given suitable parameters. Call that from a genuine mouseMoveEvent() and also call it explicitly from your code when you want to do edge scroll.

        Don't do any time.sleep(), bad idea. Anything time-based needed should use QTimer to do the stuff after the sleep() instead.

        Get those done, then see whether you still have the "view doesnt update" issue.

        E Offline
        E Offline
        elver
        wrote on last edited by elver
        #3

        @JonB Thanks for your reply! I've tried that, and the same thing happens. I think the problem stems from that I am doing the edge scroll as a loop:
        while self.cursor_pos == QCursor.pos(): adjust_view()

        Maybe that is preventing the QGraphicsview from updating, although i am calling self.update() every time inside adjust_view().
        I can see that the viewport is changing, but somehow it just isn't updating, and as soon as i exit the loop by moving the mouse it updates.

        Should using QThread solve that issue? I tried it some time ago, but couldnt make it work properly

        JonBJ 1 Reply Last reply
        0
        • E elver

          @JonB Thanks for your reply! I've tried that, and the same thing happens. I think the problem stems from that I am doing the edge scroll as a loop:
          while self.cursor_pos == QCursor.pos(): adjust_view()

          Maybe that is preventing the QGraphicsview from updating, although i am calling self.update() every time inside adjust_view().
          I can see that the viewport is changing, but somehow it just isn't updating, and as soon as i exit the loop by moving the mouse it updates.

          Should using QThread solve that issue? I tried it some time ago, but couldnt make it work properly

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by JonB
          #4

          @elver
          I did not study you code. while loops are bad :) I don't know what your code does but get rid of it. No whiles, no loops, no sleep()s. Calling update() does not do anything if inside a loop, only when your code exits and Qt gets to return to the top-level event loop does updating happen. Don't do anything "busy"!

          And BTW the very last thing you want to do here is introduce threads :)

          E 1 Reply Last reply
          1
          • JonBJ JonB

            @elver
            I did not study you code. while loops are bad :) I don't know what your code does but get rid of it. No whiles, no loops, no sleep()s. Calling update() does not do anything if inside a loop, only when your code exits and Qt gets to return to the top-level event loop does updating happen. Don't do anything "busy"!

            And BTW the very last thing you want to do here is introduce threads :)

            E Offline
            E Offline
            elver
            wrote on last edited by
            #5

            @JonB Hahaha alright thanks! what could I do other than a loop if I want to repeat the same action while the cursor is not moving? I cant imagine doing that without having a loop somewhere. Could I somehow exit the loop after every run to update everything and then go back in?

            jsulmJ 1 Reply Last reply
            0
            • E elver

              In my program, I have a QDialog that displays a picture that you can zoom and pan around, if you pan with the right mouse button clicked, you can draw a rectangle. When you are drawing a rectangle and reach the edge of the view, the picture should move so that you can keep drawing the rectangle. Right now it only works if you keep moving the cursor slightly to trigger the mouseMoveEvent.

              If I try to programatically call the mouseMoveEvent when the mouse isnt moving, to keep the picture moving, it moves the picture, but for some reason the view doesnt update, so you only see that the picture has moved after moving the mouse again. So basically if you keep the mouse on the edge for some time without moving it, and then move it, the picture moves by a lot suddenly.

              Interestingly, when I call the mouseMoveEvent programatically, for some reason the event.position() changes, even though the event is always the same one.

              Here is a minimal reproducible example:

              the adjust_view function is the original one where you have to move the cursor up and down to keep the picture moving

              the adjust_view_loop function is my attempt to programatically make it work

              Im using Python 3.10.7 and PySide6.3.2

              from PySide6.QtCore import Signal, Qt, QRectF, QPoint, QRect
              from PySide6.QtGui import QPainter, QPixmap, QImage, QPen, QCursor, QMouseEvent
              from PySide6.QtWidgets import (
                  QGraphicsView,
                  QGraphicsScene,
                  QGraphicsPixmapItem,
                  QGraphicsRectItem,
                  QApplication,
                  QDialog,
                  QDialogButtonBox,
                  QVBoxLayout,
                  QHBoxLayout,
              )
              
              import warnings
              import time
              import sys
              
              
              class MyDialog(QDialog):
                  MIN_WIDTH = 900
                  MIN_HEIGHT = int(MIN_WIDTH * 2 / 3)
              
                  def __init__(self, path, parent=None):
                      super().__init__(parent)
                      self.path = path
                      self.setFixedSize(self.MIN_WIDTH, self.MIN_HEIGHT)
              
                      self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
                      self.buttonBox.rejected.connect(self.reject)
                      self.buttonBox.accepted.connect(self.accept)
              
                      self.layout = QVBoxLayout()
                      self.image_viewer = PhotoViewer(self)
                      image = QImage(path)
                      self.pixmap = QPixmap(image)
                      self.image_viewer.set_photo(self.pixmap)
              
                      bottom_layout = QHBoxLayout()
                      bottom_layout.addWidget(self.buttonBox)
              
                      self.layout.addWidget(self.image_viewer)
                      self.layout.addLayout(bottom_layout)
                      self.setLayout(self.layout)
              
                  def exec(self):
                      self.image_viewer.viewport().setMinimumSize(
                          self.MIN_WIDTH - 100, self.MIN_HEIGHT - 100
                      )
                      self.image_viewer.fit_in_view()
                      self.image_viewer.calc_delta()
                      super().exec()
              
              
              class PhotoViewer(QGraphicsView):
                  """
                  class for displaying the pictures
                  """
              
                  DELTA = 10
                  mouse_event_signal = Signal()
              
                  def __init__(self, parent):
                      super(PhotoViewer, self).__init__(parent)
                      warnings.filterwarnings("error")
                      self.zoom = 0
                      self._empty = True
                      self._photo = QGraphicsPixmapItem()
                      self._photo.setTransformationMode(Qt.SmoothTransformation)
              
                      self.set_scene()
                      self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
                      self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
                      self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
                      self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
                      self.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.Antialiasing)
              
                  def set_scene(self):
                      self.scene = SelectColorBarDialogScene(self)
                      self.scene.addItem(self._photo)
                      self.setScene(self.scene)
              
                  def fit_in_view(self, scale=True):
                      """
                      Resizes the photo to fit in the view
                      """
                      rect = QRectF(self._photo.pixmap().rect())
                      if not rect.isNull():
                          self.setSceneRect(rect)
                          unity = self.transform().mapRect(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.zoom = 0
              
                  def mousePressEvent(self, event):
                      if event.buttons() & Qt.RightButton:
                          self.update()
                      super(PhotoViewer, self).mousePressEvent(event)
              
                  def mouseMoveEvent(self, event, pass_adjust=False):
                      super(PhotoViewer, self).mouseMoveEvent(event)
                      if event.buttons() & Qt.RightButton:
                          if not self.scene.begin.isNull() and not self.scene.destination.isNull():
                              if not pass_adjust:
                                  # self.adjust_view(event)
                                  self.adjust_view_loop(event)
              
                              print(event.globalPosition())
                              print(event.position())
                              print("_______________")
              
                          self.update()
              
                  #! When programatically changed, the mouse pos changes even though cursor is not moved
                  def adjust_view_loop(self, event):
                      edges = self.check_if_point_on_edge(self.scene.destination)
              
                      if (
                          edges
                          and self.scene.destination.x() < self.photo_size.width() - self.DELTA
                          and self.scene.destination.y() < self.photo_size.height() - self.DELTA
                          and self.scene.destination.x() > self.DELTA
                          and self.scene.destination.y() > self.DELTA
                      ):
                          i = 0
                          self.cursor_pos = QCursor.pos()
                          while True:
                              i += 1
                              old_view = self.mapToScene(self.viewport().rect()).boundingRect()
                              new_view = [0, 0, 0, 0]
                              for _ in range(edges.bit_count()):
                                  if edges & 1 == 1:
                                      new_view[1] = -self.DELTA
                                      new_view[3] = -self.DELTA
                                  elif edges & 4 == 4:
                                      new_view[1] = self.DELTA
                                      new_view[3] = self.DELTA
                                  elif edges & 2 == 2:
                                      new_view[0] = -self.DELTA
                                      new_view[2] = -self.DELTA
                                  elif edges & 8 == 8:
                                      new_view[0] = self.DELTA
                                      new_view[2] = self.DELTA
                              new_view_rect = old_view.adjusted(
                                  new_view[0], new_view[1], new_view[2], new_view[3]
                              )
                              self.fitInView(new_view_rect, Qt.KeepAspectRatio)
                              x = self.scene.destination.x()
                              y = self.scene.destination.y()
                              self.set_rect()
                              if i % 2 == 0:
                                  new_event = QMouseEvent(
                                      event.type(),
                                      QPoint(event.position().x(), event.position().y() + 1),
                                      QPoint(
                                          event.globalPosition().x(), event.globalPosition().y() + 1
                                      ),
                                      event.button(),
                                      event.buttons(),
                                      event.modifiers(),
                                  )
                              else:
                                  new_event = QMouseEvent(
                                      event.type(),
                                      QPoint(event.position().x(), event.position().y() - 1),
                                      QPoint(
                                          event.globalPosition().x(), event.globalPosition().y() - 1
                                      ),
                                      event.button(),
                                      event.buttons(),
                                      event.modifiers(),
                                      event.pointingDevice(),
                                  )
                              self.mouseMoveEvent(new_event, True)
                              self.update()
                              print("Looping...")
                              time.sleep(0.5)
                              if not self.on_loop_signal():
                                  break
              
                      self.set_rect()
              
                  def adjust_view(self, event):
                      edges = self.check_if_point_on_edge(self.scene.destination)
              
                      if (
                          edges
                          and self.scene.destination.x() < self.photo_size.width() - self.DELTA
                          and self.scene.destination.y() < self.photo_size.height() - self.DELTA
                          and self.scene.destination.x() > self.DELTA
                          and self.scene.destination.y() > self.DELTA
                      ):
                          old_view = self.mapToScene(self.viewport().rect()).boundingRect()
                          new_view = [0, 0, 0, 0]
                          for _ in range(edges.bit_count()):
                              if edges & 1 == 1:
                                  new_view[1] = -self.DELTA
                                  new_view[3] = -self.DELTA
                              elif edges & 4 == 4:
                                  new_view[1] = self.DELTA
                                  new_view[3] = self.DELTA
                              elif edges & 2 == 2:
                                  new_view[0] = -self.DELTA
                                  new_view[2] = -self.DELTA
                              elif edges & 8 == 8:
                                  new_view[0] = self.DELTA
                                  new_view[2] = self.DELTA
                          new_view_rect = old_view.adjusted(
                              new_view[0], new_view[1], new_view[2], new_view[3]
                          )
                          self.fitInView(new_view_rect, Qt.KeepAspectRatio)
                          x = self.scene.destination.x()
                          y = self.scene.destination.y()
                          self.set_rect()
              
                      self.set_rect()
              
                  def set_rect(self):
                      for item in self.scene.items():
                          if isinstance(item, QGraphicsRectItem):
                              self.scene.removeItem(item)
                      self.rect = QRect(self.scene.begin, self.scene.destination)
                      self.scene.addRect(self.rect.normalized(), QPen(Qt.red))
              
                  def get_rect(self):
                      return self.rect.normalized()
              
                  def mouseReleaseEvent(self, event):
                      if event.button() & Qt.RightButton:
                          self.set_rect()
                          self.update()
                      super(PhotoViewer, self).mouseReleaseEvent(event)
              
                  def check_if_point_on_edge(self, point):
                      rect = self.mapToScene(
                          self.viewport().rect()
                      ).boundingRect()  # left, top, width, height
                      edges = 0  # top = 1, bottom = 2, left = 4, right = 8
                      internal_rect = rect.adjusted(self.DELTA, self.DELTA, -self.DELTA, -self.DELTA)
                      if rect.contains(point) and not internal_rect.contains(point):
                          if point.x() < internal_rect.left():
                              edges = edges | 2
                          elif point.x() > internal_rect.right():
                              edges = edges | 8
                          if point.y() < internal_rect.top():
                              edges = edges | 1
                          elif point.y() > internal_rect.bottom():
                              edges = edges | 4
              
                      return edges
              
                  def wheelEvent(self, event):
                      self.calc_delta()
                      if event.angleDelta().y() > 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.fit_in_view()
                      else:
                          self.zoom = 0
              
                  def calc_delta(self):
                      rect_width = self.mapToScene(self.viewport().rect()).boundingRect().width()
                      self.DELTA = int(rect_width / 25)
              
                  def set_photo(self, pixmap=None):
                      """
                      Sets the photo to be displayed, if pixmap is None, the photo is set to empty
                      """
                      if pixmap and not pixmap.isNull():
                          self._empty = False
                          self.setDragMode(QGraphicsView.ScrollHandDrag)
                          self._photo.setPixmap(pixmap)
                      else:
                          self._empty = True
                          self.setDragMode(QGraphicsView.NoDrag)
                          self._photo.setPixmap(QPixmap())
                      self.photo_size = QRectF(self._photo.pixmap().rect())
              
                  def on_loop_signal(self):
                      if self.cursor_pos != QCursor.pos():
                          self.mouse_event_signal.emit()
                          return False
                      return True
              
              
              class SelectColorBarDialogScene(QGraphicsScene):
                  def __init__(self, parent):
                      super(SelectColorBarDialogScene, self).__init__(parent)
                      self.begin, self.destination = QPoint(), QPoint()
              
                  def mousePressEvent(self, event):
                      if event.buttons() & Qt.RightButton:
                          self.begin = event.scenePos().toPoint()
                          self.destination = self.begin
                          self.update()
                      super(SelectColorBarDialogScene, self).mousePressEvent(event)
              
                  def mouseMoveEvent(self, event):
                      if event.buttons() & Qt.RightButton:
                          self.destination = event.scenePos().toPoint()
                          self.update()
                      super(SelectColorBarDialogScene, self).mouseMoveEvent(event)
              
                  def mouseReleaseEvent(self, event):
                      if event.button() & Qt.RightButton:
                          self.begin, self.destination = QPoint(), QPoint()
                          self.update()
                      super(SelectColorBarDialogScene, self).mouseReleaseEvent(event)
              
              
              if __name__ == "__main__":
                  app = QApplication(sys.argv)
                  dialog = MyDialog(path=None)  #! set picture path here
                  dialog.exec()
                  sys.exit(app.exec_())
              
              jeremy_kJ Offline
              jeremy_kJ Offline
              jeremy_k
              wrote on last edited by
              #6

              @elver said in In a QGraphicsview, how can I keep the image moving when the cursor is on the edge, without having to move the mouse?:

              from PySide6.QtCore import Signal, Qt, QRectF, QPoint, QRect
              

              [...]

              This is too large for a minimal working example. Cut out all of the stuff unrelated to the problem.

              Here is an example of detecting a mouse dragged outside of a window, with an operation repeated until the button is released or the cursor moves back over it:

              from PyQt5 import QtWidgets, QtCore, QtGui
              
              class View(QtWidgets.QGraphicsView):
                  def __init__(self, parent=None):
                      super().__init__(parent)
                      self.scene = QtWidgets.QGraphicsScene()
                      self.workTimer = QtCore.QTimer()
                      self.workTimer.setInterval(33)
                      self.workTimer.timeout.connect(self.autoscroll)
              
                  def mouseMoveEvent(self, event):
                      self.position = event.pos()
                      if self.autoscroll():
                          self.workTimer.start()
                      else:
                          self.workTimer.stop()
                          super().mouseMoveEvent(event)
              
                  def mouseReleaseEvent(self, event):
                      self.workTimer.stop()
                      super().mouseReleaseEvent(event)
              
                  def autoscroll(self):
                      geometry = self.geometry()
                      if not geometry.contains(self.position):
                          if self.position.x() < 0:
                              print("-x")
                          elif self.position.x() >= geometry.width():
                              print("+x")
                          elif self.position.y() < 0:
                              print("-y")
                          elif self.position.y() >= geometry.height():
                              print("+y")
                          return True
                      else:
                          return False
              
              
              app = QtWidgets.QApplication([])
              view = View()
              view.show()
              app.exec()
              

              Asking a question about code? http://eel.is/iso-c++/testcase/

              1 Reply Last reply
              2
              • E elver

                @JonB Hahaha alright thanks! what could I do other than a loop if I want to repeat the same action while the cursor is not moving? I cant imagine doing that without having a loop somewhere. Could I somehow exit the loop after every run to update everything and then go back in?

                jsulmJ Offline
                jsulmJ Offline
                jsulm
                Lifetime Qt Champion
                wrote on last edited by
                #7

                @elver said in In a QGraphicsview, how can I keep the image moving when the cursor is on the edge, without having to move the mouse?:

                what could I do other than a loop if I want to repeat the same action while the cursor is not moving?

                Use QTimer

                https://forum.qt.io/topic/113070/qt-code-of-conduct

                1 Reply Last reply
                1
                • E Offline
                  E Offline
                  elver
                  wrote on last edited by
                  #8

                  Using QTimer it works, what a nice functionality. Thank you everyone for your input!

                  1 Reply Last reply
                  1
                  • E elver has marked this topic as solved on

                  • Login

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Categories
                  • Recent
                  • Tags
                  • Popular
                  • Users
                  • Groups
                  • Search
                  • Get Qt Extensions
                  • Unsolved