How to get mouse release coordinates in QGraphicsView - python

Hi I'm new to Qt and pyside. I'm trying to get the coordinates of mouse in a QGraphicsView instance. I tried to reimplement my mouseReleaseEvent but wondering how would I actually use this reimplemented function.
In MainWindow class:
self.tScn = QtGui.QGraphicsScene()
self.graphicsView_2 = QtGui.QGraphicsView(self.centralwidget, self.tScn)
In MainConsumer class(derived from MainWindow:
def pointSelection(self):
pos = self.tScn.mouseReleaseEvent(QMouseEvent)
print(pos)
def mouseReleaseEvent(self, QMouseEvent):
pos = QMouseEvent.lastScenePos()
print(pos)
return pos
python gives me this warning:
AttributeError: 'PySide.QtGui.QMouseEvent' object has no attribute 'lastScenePos'
I tried couple of different orders and structures but nothing worked and now I am really confused by the relationship between PySide.QtGui.QGraphicsScene.mouseReleaseEvent(event), PySide.QtGui.QGraphicsSceneMouseEvent.lastScenePos(), class PySide.QtGui.QGraphicsSceneMouseEvent([type=None]) and QtCore.QEvent.GraphicsSceneMouseRelease could somebody help me?
Thanks!

Create a class that inherits from QGraphicsScene and has a signal like this
class MyGraphicsScene(QtGui.QGraphicsScene):
signalMousePos = QtCore.pyqtSignal(QtCore.QPointF)
def __init__(self, parent):
super(MyGraphicsScene, self).__init__(parent)
and then override the mouseRelease event in this new class
def mouseReleaseEvent(QGraphicsSceneMouseEvent):
pos = QGrapihcsSceneMouseEvent.lastScenePos()
self.signalMousePos.emit(pos)
Then in your MainConsumer class replace
self.tScn = QtGui.QGraphicsScene()
with
self.tScn = MyQGraphicsScene()
self.tScn.signalMousePos.connect(self.pointSelection)
the pointSelection becomes
def pointSelection(self, pos)
#Whatever you want to do with the position coordinates
and mouseReleaseEvent in MainConsumer is no longer necessary

Related

In Kivy programming, where should buttons be created/resized/repositioned in a class created to be used other classes?

#This class is to be used with other classes of widgets, not with the class of kivy.app.
class TempatureFloatLayout(FloatLayout):
tempature = NumericProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_interval(self.update, 1)
self.btn = Button()
self.btn.bind(on_press=self.do_something)
self.add_widget(self.btn)
def do_something(self, self_button):
pass
def update(self, dt):
self.btn.size = self.size # I can only resize and reposition the button here.
self.btn.pos = self.pos # pos and size are assigned with the pos and size
# values of the widget used
... the other codes
class TempatureFloatLayout(FloatLayout) can be used successfully in other classes. The code works correctly as it is. But, When every update, the position and size of the button is to be resized and repositioned. This doesn't feel right to me. How to bind a widget that is used and a button that is used in a different class. Where did I do wrong or is this usage correct?

Create a QGraphicsItem at the mouse double-click position

I'm writing a program that should create a QGraphicsItem when a double-click happens (inside the scene area), and also that item must be created at double-click position. I've already written some code, but it doesn't work properly. When I double-click on the scene, an item gets created, but at a completely different place. So right now that is the problem. I need it to be created at mouse's position.
Here is my code:
ui_path = "C:/Users/User/Desktop/programming/Creator/Creator.ui"
class Creator(QtWidgets.QWidget):
count = 0
def __init__(self):
super(Creator, self).__init__()
loader = QtUiTools.QUiLoader()
self.ui = loader.load(ui_path, self)
self.variables()
self.ui.canvas_area.viewport().installEventFilter(self) #canvas_area is the QGraphicsView
self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint)
def variables(self):
self.scene = QtWidgets.QGraphicsScene()
self.ui.canvas_area.setScene(self.scene)
def eventFilter(self, obj, event):
if obj is self.ui.canvas_area.viewport():
if event.type() == QtCore.QEvent.MouseButtonDblClick:
self.createItems(event)
return super(Creator, self).eventFilter(obj, event)
def createItems(self, event):
pos = event.pos()
self._x = pos.x()
self._y = pos.y()
rect = self.scene.addRect(self._x, self._y, 40, 40, QPen(Qt.red), QBrush(Qt.gray))
rect.setFlag(QGraphicsItem.ItemIsMovable)
rect.setFlag(QGraphicsItem.ItemIsFocusable)
rect.setFlag(QGraphicsItem.ItemIsSelectable)
if __name__ == '__main__':
creator_window = Creator()
creator_window.ui.show()
I've read something about mapFromScene and mapToScene, and that these can solve the problem - but I don't really understand how to use them. Also there are some examples of this, but all that I've found were in C++, which I know nothing about. So if someone could help me figure out how to solve this problem I would really appreciate that.
You first must set the scene-rect for the scene:
self.scene.setSceneRect(0, 0, 1000, 1000)
then you must convert the event position to scene co-ordinates, like this:
pos = self.canvas_area.mapToScene(event.pos())
and that should be all that's needed to fix your example.
A different approach that may be worth considering would be to install an event-filter on the scene instead:
self.scene.installEventFilter(self)
and then filter on the more specialised graphics events:
def eventFilter(self, obj, event):
if obj is self.scene:
if event.type() == QtCore.QEvent.GraphicsSceneMouseDoubleClick:
self.createItems(event)
return super(Creator, self).eventFilter(obj, event)
This creates a QGraphicsSceneMouseEvent, which has several useful features that a standard QMouseEvent doesn't have. This includes scenePos(), which saves having to map to scene co-ordinates all the time, allowing you to then simply do:
pos = event.scenePos()

Clear a QGraphicsScene from an event of one of the items

I have a QGraphicsScene with many QGraphicsItem. Some items have buttons that clear and repaint the scene.
The problem is that the clear() method deletes the QButton (and its
associated data structures) in the middle of a method call that uses
those very data structures. Then, immediately after clear() returns,
the calling method tries to access the now-deleted data (because it
wasn't expecting to be deleted in the middle of its routine), and bang
-- a crash. From here.
I found the solution for C ++ here, however I am using PySide and could not use the same solution for python.
Follow my code:
class GraphicsComponentButtonItem(QtGui.QGraphicsItem):
def __init__(self, x, y, update_out):
super(GraphicsComponentButtonItem, self).__init__()
self.x = x
self.y = y
self.update_out = update_out
self.setPos(x, y)
self.createButton()
self.proxy = QtGui.QGraphicsProxyWidget(self)
self.proxy.setWidget(self.button)
def boundingRect(self):
return QtCore.QRectF(self.x, self.y, self.button.width(), self.button.height())
def paint(self, painter, option, widget):
# Paint same stuffs
def createButton(self):
self.button = QtGui.QPushButton()
self.button.setText('Clear')
self.button.clicked.connect(self.action_press)
def action_press(self):
# Run some things
self.update_out()
class QGraphicsViewButtons(QtGui.QGraphicsView):
def __init__(self, scene, parent=None):
QtGui.QGraphicsView.__init__(self, parent)
self.scene = scene
# It's called outside
def updateScene(self):
self.scene.clear()
self.scene.addItem(GraphicsComponentButtonItem(0, 0, self.updateScene))
self.scene.addItem(GraphicsComponentButtonItem(0, 50, self.updateScene))
self.scene.addItem(GraphicsComponentButtonItem(0, 100, self.updateScene))
the conversion of the following C++ code:
QObject::connect(button, SIGNAL(clicked()), scene, SLOT(clear()), Qt::QueuedConnection);
to python is:
self.button.clicked.connect(self.scene().clear, QtCore.Qt.QueuedConnection)

Qgraphicsview items not being placed where they should be

I recently created a program that will create QgraphicsEllipseItems whenever the mouse is clicked. That part works! However, it's not in the exact place where my cursor is. It seems to be slightly higher than where my mouse cursor is. I do have a QGraphicsRectItem created so maybe the two items are clashing with each other and moving off of one another? How can I get these circles to be placed on top of the rectangle item? Here's the code
class MyView(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
self.scene = QtGui.QGraphicsScene(self)
self.item = QtGui.QGraphicsRectItem(400, 400, 400, 400)
self.scene.addItem(self.item)
self.setScene(self.scene)
def paintMarkers(self):
self.cursor = QtGui.QCursor()
self.x = self.cursor.pos().x()
self.y = self.cursor.pos().y()
self.circleItem = QtGui.QGraphicsEllipseItem(self.x,self.y,10,10)
self.scene.addItem(self.circleItem)
self.circleItem.setPen(QtGui.QPen(QtCore.Qt.red, 1.5))
self.setScene(self.scene)
class Window(QtGui.QMainWindow):
def __init__(self):
#This initializes the main window or form
super(Window,self).__init__()
self.setGeometry(50,50,1000,1000)
self.setWindowTitle("Pre-Alignment system")
self.view = MyView()
self.setCentralWidget(self.view)
def mousePressEvent(self,QMouseEvent):
self.view.paintMarkers()
Much thanks!
There are two issues with the coordinates you are using to place the QGraphics...Items. The first is that the coordinates from QCursor are global screen coordinates, so you need to use self.mapFromGlobal() to convert them to coordinates relative to the QGraphicsView.
Secondly, you actually want the coordinates relative to the current QGraphicsScene, as this is where you are drawing the item. This is because the scene can be offset from the view (for example panning around a scene that is bigger than a view). To do this, you use self.mapToScene() on the coordinates relative to the QGraphicsView.
I would point out that typically you would draw something on the QGraphicsScene in response to some sort of mouse event in the QGraphicsView, which requires reimplementing things like QGraphicsView.mouseMoveEvent or QGraphicsView.mousePressEvent. These event handlers are passed a QEvent which contains the mouse coordinates relative to the view, and so you don't need to do the global coordinates transformation I mentioned in the first paragraph in these cases.
Update
I've just seen your other question, and now understand some of the issue a bit better. You shouldn't be overriding the mouse event in the main window. Instead override it in the view. For example:
class MyView(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
self.scene = QtGui.QGraphicsScene(self)
self.item = QtGui.QGraphicsRectItem(400, 400, 400, 400)
self.scene.addItem(self.item)
self.setScene(self.scene)
def paintMarkers(self, event):
# event position is in coordinates relative to the view
# so convert them to scene coordinates
p = self.mapToScene(event.x(), event.y())
self.circleItem = QtGui.QGraphicsEllipseItem(0,0,10,10)
self.circleItem.setPos(p.x()-self.circleItem.boundingRect().width()/2.0,
p.y()-self.circleItem.boundingRect().height()/2.0)
self.scene.addItem(self.circleItem)
self.circleItem.setPen(QtGui.QPen(QtCore.Qt.red, 1.5))
# self.setScene(self.scene) # <-- this line should not be needed here
# Note, I've renamed the second argument `event`. Otherwise you locally override the QMouseEvent class
def mousePressEvent(self, event):
self.paintMarkers(event)
# you may want to preserve the default mouse press behaviour,
# in which case call the following
return QGraphicsView.mousePressEvent(self, event)
Here we have not needed to use QWidget.mapFromGlobal() (what I covered in the first paragraph) because we use a mouse event from the QGraphicsView which returns coordinates relative to that widget only.
Update 2
Note: I've updated how the item is created/placed in the above code based on this answer.

addText() change the text color inside a QGraphicsView

I have managed to make a vertical text label inside a QGraphicsView which is imported from showGui.UI. Everything is exactly how I want it except I can't figure out to change the text color. setStyleSheet doesn't work with QGraphicsViews. I made another attempt with QPainter but i couldn't get that to roate correctly or inside of my graphics view. All the documentation I have found is in C++ and that leaves me bewildered. I think adding:
self.trans_graphicsView_cat.drawForeground(QPainter(setPen(QtGui.QColor(168, 34, 3)))
into the createScene Function will do it but I haven't been able to crack it.
My Code:
class MainDialog(QtGui.QMainWindow, showGui.Ui_MainWindow):
dbPath = appDataPath + "barter.db"
dbConn = sqlite3.connect(dbPath)
dbConn.text_factory = str
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.text = self.tr("Categories")
self.createScene()
def createScene(self):
scene = QtGui.QGraphicsScene()
self.trans_graphicsView_cat.setScene(scene)
item = scene.addText(self.text, QtGui.QFont('Arial Black', 15, QtGui.QFont.Light))
item.rotate(270)
The addText method returns a QGraphicsTextItem. This class has a setDefaultTextColor method which should allow you to change the colour of the text.
Alternatively, you could create your own instance of QGraphicsTextItem and then use addItem to add it to the scene. Note that the QGraphicsTextItem class has a setHtml method which should give you even greater control over the formatting of the text.

Categories

Resources