1

I have been trying to learn a bit more about the QGraphicsView widget by taking a look at this example here:

Toggle QPen for selected items after QGraphicsScene selection change

I found this fairly helpful, however i noticed that upon selection there is marquee thing that pops up when an item is selected. I was looking at ways to get rid of this, and it seems like there is a consistent answer to this in C++ which I see as an example here:

https://www.qtcentre.org/threads/39659-About-QGraphicsItem-question

But i am very confused as to how to translate this into pyqt, so I was wondering if anyone can provide any insight to how to do this within the example project from the first link....

I've been searching around for possible equivalents by looking at codes such as this:

https://www.riverbankcomputing.com/pipermail/pyqt/2012-November/032110.html

Although to be honest I don't even know what the application of these example codes are...

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
user3696118
  • 343
  • 3
  • 17
  • Sorry, I read your question badly, I already reopened your question. On the other hand what code do you mean since there are many codes on the other link so it causes me confusion – eyllanesc Aug 15 '19 at 06:14
  • Hi there. for the first link i was referring to the edited code that was in the answers. which when run seems ro work fine. so when i looked up ways to get rid of the dotted rectangle i found multiple examples in c++ of how to achieve this by editing the QStyleOptionGraphicsItem and messing around with some attribute called State_Selected. and something to do with opt.state (second link). and so in the third link i found an example in pyqt that references opt.state. but i dont inow what it is doing in that instance... – user3696118 Aug 15 '19 at 06:20

1 Answers1

2

If you want to remove the rectangle from the selection of the item in python you must use the following:

class GraphicsEllipseItem(QGraphicsEllipseItem):
    def paint(self, painter, option, widget):
        option.state &= ~QStyle.State_Selected
        super(GraphicsEllipseItem, self).paint(painter, option, widget)

Complete script

import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import random


class GraphicsEllipseItem(QGraphicsEllipseItem):
    def paint(self, painter, option, widget):
        option.state &= ~QStyle.State_Selected
        super(GraphicsEllipseItem, self).paint(painter, option, widget)


class MyGraphicsView(QGraphicsView):
    def __init__(self):
        super(MyGraphicsView, self).__init__()
        self.setDragMode(QGraphicsView.RubberBandDrag)
        self._isPanning = False
        self._mousePressed = False
        self.setCacheMode(QGraphicsView.CacheBackground)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setScene(MyGraphicsScene(self))
        self.scene().selectionChanged.connect(self.selection_changed)
        self._current_selection = []

    def select_items(self, items, on):
        pen = QPen(
            QColor(255, 255, 255) if on else QColor(255, 128, 0),
            0.5,
            Qt.SolidLine,
            Qt.RoundCap,
            Qt.RoundJoin,
        )
        for item in items:
            item.setPen(pen)

    def selection_changed(self):
        try:
            self.select_items(self._current_selection, False)
            self._current_selection = self.scene().selectedItems()
            self.select_items(self._current_selection, True)
        except RuntimeError:
            pass

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self._mousePressed = True
            if self._isPanning:
                self.setCursor(Qt.ClosedHandCursor)
                self._dragPos = event.pos()
                event.accept()
            else:
                super(MyGraphicsView, self).mousePressEvent(event)
        elif event.button() == Qt.MiddleButton:
            self._mousePressed = True
            self._isPanning = True
            self.setCursor(Qt.ClosedHandCursor)
            self._dragPos = event.pos()
            event.accept()

    def mouseMoveEvent(self, event):
        if self._mousePressed and self._isPanning:
            newPos = event.pos()
            diff = newPos - self._dragPos
            self._dragPos = newPos
            self.horizontalScrollBar().setValue(
                self.horizontalScrollBar().value() - diff.x()
            )
            self.verticalScrollBar().setValue(
                self.verticalScrollBar().value() - diff.y()
            )
            event.accept()
        else:
            super(MyGraphicsView, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            if self._isPanning:
                self.setCursor(Qt.OpenHandCursor)
            else:
                self._isPanning = False
                self.setCursor(Qt.ArrowCursor)
            self._mousePressed = False
        elif event.button() == Qt.MiddleButton:
            self._isPanning = False
            self.setCursor(Qt.ArrowCursor)
            self._mousePressed = False
        super(MyGraphicsView, self).mouseReleaseEvent(event)

    def mouseDoubleClickEvent(self, event):
        self.fitInView(self.sceneRect(), Qt.KeepAspectRatio)
        pass

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Space and not self._mousePressed:
            self._isPanning = True
            self.setCursor(Qt.OpenHandCursor)
        else:
            super(MyGraphicsView, self).keyPressEvent(event)

    def keyReleaseEvent(self, event):
        if event.key() == Qt.Key_Space:
            if not self._mousePressed:
                self._isPanning = False
                self.setCursor(Qt.ArrowCursor)
        else:
            super(MyGraphicsView, self).keyPressEvent(event)

    def wheelEvent(self, event):
        # zoom factor
        factor = 1.25

        # Set Anchors
        self.setTransformationAnchor(QGraphicsView.NoAnchor)
        self.setResizeAnchor(QGraphicsView.NoAnchor)

        # Save the scene pos
        oldPos = self.mapToScene(event.pos())

        # Zoom
        if event.delta() < 0:
            factor = 1.0 / factor
        self.scale(factor, factor)

        # Get the new position
        newPos = self.mapToScene(event.pos())

        # Move scene to old position
        delta = newPos - oldPos
        self.translate(delta.x(), delta.y())


class MyGraphicsScene(QGraphicsScene):
    def __init__(self, parent):
        super(MyGraphicsScene, self).__init__(parent)
        self.setBackgroundBrush(QBrush(QColor(50, 50, 50)))
        # self.setSceneRect(50,50,0,0)


class MyMainWindow(QMainWindow):
    def __init__(self):
        super(MyMainWindow, self).__init__()
        self.setWindowTitle("Test")
        self.resize(800, 600)
        self.gv = MyGraphicsView()
        self.setCentralWidget(self.gv)
        self.populate()

    def populate(self):
        scene = self.gv.scene()
        for i in range(500):
            x = random.randint(0, 1000)
            y = random.randint(0, 1000)
            r = random.randint(2, 8)
            rect = GraphicsEllipseItem(x, y, r, r)
            rect.setPen(
                QPen(QColor(255, 128, 0), 0.5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
            )
            rect.setBrush(QBrush(QColor(255, 128, 20, 128)))
            scene.addItem(rect)
            rect.setFlag(QGraphicsItem.ItemIsSelectable)
            rect.setFlag(QGraphicsItem.ItemIsMovable)

        rect = GraphicsEllipseItem(300, 500, 20, 20)
        rect.setPen(
            QPen(QColor(255, 128, 0), 0.5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        )
        rect.setBrush(QBrush(QColor(255, 0, 0, 128)))
        scene.addItem(rect)
        rect.setFlag(QGraphicsItem.ItemIsSelectable)
        rect.setFlag(QGraphicsItem.ItemIsMovable)


def main():
    app = QApplication(sys.argv)
    ex = MyMainWindow()
    ex.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Explanation of option.state &= ~QStyle.State_Selected:

state is an attribute of QStyleOptionGraphicsItem that stores the information of the state of the painting that may contain a combination of the following flags:

enter image description here

In this case the flags can be combined with using the "|" operator, and deactivate using the "&~".

To understand it better, let's use the following example: we set the state in State_Active and State_Editing as initial state:

state = State_Active | State_Editing
      = 0x00010000 | 0x00400000
      = 0x00410000

And to deactivate the State_Active flag:

state & ~State_Active
0x00410000 & ~(0x00010000)
0x00410000 &   0xFFFEFFFF
0x00400000

As you can see, flag State_Active is removed without removing other flags only with binary operations.

So how do you want to deactivate the State_Selected flag and replace it to the state then it must be done:

option.state = option.state & ~QStyle.State_Selected
option.state &= ~QStyle.State_Selected
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • thank you for your answer! Just like to clarify what is going on here. If i remember correctly I believe the tilda symbol in C++ was used to destroy something out of memory (or something like that). Is that what is happening here? Also this is using the same "&=" operator that I saw in the C++ example, what is the actual meaning behind this...? – user3696118 Aug 15 '19 at 14:04
  • ah i see. so basically this use of & operator is to remove this "State_Selected" flag via binary method without affecting the other flags or "State_*"? – user3696118 Aug 15 '19 at 14:36
  • Hi there, yes sorry it took me a while to test out your answer. And it seems like it works like a charm! thank you so much for your help! – user3696118 Aug 16 '19 at 00:31