Search code examples
pythonqtpysideqgraphicsviewqgraphicsscene

Make QGraphicsItem only selectable in one graphicsview


I have a setup where two QGraphicViews display a single QGraphicsScene. One of these views is an overview the other the detail. Imagine something like:

enter image description here

The rectangle marking the current boundaries of the detailed view is part of the scene. It is the white rectangle in the upper view, which I will call in the text below as "bounding-box".

What I want is to be able to click in the overview- QGraphicsView and drag the bounding-box around to trigger a scrolling of the detail- QGraphicsView. Obviously, the bounding-box has to be only clickable in the overview- QGraphicsView, otherwise I would never be able to do manipulations in the detail- QGraphicsView, because the bounding-box covers the entire detail view.

So how can I make a QGraphicsItem be selectable only from a single QGraphicsView or, alternatively, how do I "insert" a QGraphicsItem only into a single QGraphicsView? Can I perhaps nest QGraphicsScenes so that one is the copy of another plus some extra items?


Solution

  • Extending the answer of Trilarion, I was able to solve the problem, by installing a Eventfilter on the overview QgraphcisView. On the Enter event, the dragging is enabled, on the Leave event the dragging is disabled.

    from PySide import QtGui, QtCore
    
    # special GraphicsRectItem that is aware of its position and does something if the position is changed
    class MovableGraphicsRectItem(QtGui.QGraphicsRectItem):
    
        def __init__(self, callback=None):
            super(MovableGraphicsRectItem, self).__init__()
            self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
            self.setCursor(QtCore.Qt.PointingHandCursor)
            self.callback = callback
    
        def itemChange(self, change, value):
            if change == QtGui.QGraphicsItem.ItemPositionChange and self.callback:
                self.callback(value)
    
            return super(MovableGraphicsRectItem, self).itemChange(change, value)
    
        def activate(self):
            self.setFlags(QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
            self.setCursor(QtCore.Qt.PointingHandCursor)
    
        def deactivate(self):
            self.setFlags(not QtGui.QGraphicsItem.ItemIsMovable | QtGui.QGraphicsItem.ItemSendsScenePositionChanges)
            self.setCursor(QtCore.Qt.ArrowCursor)
    
    
    class MouseInsideFilterObj(QtCore.QObject):#And this one
        def __init__(self, enterCallback, leaveCallback):
            QtCore.QObject.__init__(self)
            self.enterCallback = enterCallback
            self.leaveCallback = leaveCallback
    
        def eventFilter(self, obj, event):
            if event.type() == QtCore.QEvent.Type.Enter:
                self.enterCallback(obj)
    
            if event.type() == QtCore.QEvent.Type.Leave:
                self.leaveCallback(obj)
    
            return True
    
    
    class TestClass:
    
        def __init__(self):
    
            self.app = QtGui.QApplication([])
    
            # the scene with some rectangles
            self.scene = QtGui.QGraphicsScene()
            self.scene.addRect(30, 30, 100, 50, pen=QtGui.QPen(QtCore.Qt.darkGreen))
            self.scene.addRect(150, 0, 30, 80, pen=QtGui.QPen(QtCore.Qt.darkYellow))
            self.scene.addRect(80, 80, 100, 20, pen=QtGui.QPen(QtCore.Qt.darkMagenta))
            self.scene.addRect(200, 10, 30, 80, pen=QtGui.QPen(QtCore.Qt.darkRed))
    
            self.window = QtGui.QWidget()
    
            # put two graphicsviews into the window with different scaling for each
            self.layout = QtGui.QVBoxLayout(self.window)
            self.v1 = QtGui.QGraphicsView(self.scene)
            self.v1.setFixedSize(500, 100)
            self.v1.scale(0.5, 0.5)
            self.v1.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
            self.v1.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
            self.layout.addWidget(self.v1) 
            self.v2 = QtGui.QGraphicsView(self.scene)
            self.v2.setFixedSize(500, 500)
            self.v2.scale(5, 5)
            self.v2.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
            self.v2.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
            self.layout.addWidget(self.v2)
    
            mouseFilter = MouseInsideFilterObj(self.enterGV, self.leaveGV)
            self.v1.installEventFilter(mouseFilter)
    
            # the tracker rectangle
            self.tracker = MovableGraphicsRectItem(lambda pos: self.v2.setSceneRect(pos.x(), pos.y(), 100, 100))
            self.tracker.setRect(0, 0, 100, 100)
            self.v2.setSceneRect(0, 0, 100, 100)
            self.tracker.setPen(QtGui.QPen(QtCore.Qt.darkCyan))
            self.scene.addItem(self.tracker)
    
            self.window.show()
            self.app.exec_()
    
        def leaveGV(self, gv):
            if gv is self.v1:
                self.tracker.deactivate()
    
        def enterGV(self, gv):
            if gv is self.v1:
                self.tracker.activate()
    
    
    
    
    
    TestClass()