PyQt/PySide QGraphicsScene, Hover event on Ellipse Item not working - python

I'm currently learning how to use Qt's Qgraphics and i cant get the hoverEnterEvent to work on an QGraphicsEllipseItem object. I am setting setAcceptHoverEvents to True and implemented the hoverEnterEvent function, yet the function isn't getting called.
here's my code:
import sys
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QGraphicsView,
QGraphicsScene,
QGraphicsEllipseItem,
)
from PySide6.QtGui import QPainterPath, QTransform, QPen, QBrush, QColor, QPainter
from PySide6.QtCore import Qt
PORT_PEN_COLOR = "#000000"
PORT_BRUSH_COLOR = "#ebebeb"
EDGE_PEN_COLOR = "#474747"
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(0, 0, 800, 600)
self.setCentralWidget(GraphicsView())
self.show()
class GraphicsView(QGraphicsView):
def __init__(self, parent=None):
super().__init__(parent)
self.setMouseTracking(True)
self.setScene(GraphicsScene())
self.setRenderHint(QPainter.RenderHint.Antialiasing)
class GraphicsScene(QGraphicsScene):
def __init__(self, parent=None):
super().__init__(parent)
self.setSceneRect(-10000, -10000, 20000, 20000)
self._port_pen = QPen(QColor(PORT_PEN_COLOR))
self._port_brush = QBrush(QColor(PORT_BRUSH_COLOR))
self._edge_pen = QPen(QColor(EDGE_PEN_COLOR))
self._edge_pen.setWidth(4)
def mousePressEvent(self, event):
clicked_item = self.itemAt(event.scenePos(), QTransform())
if event.buttons() == Qt.MouseButton.LeftButton:
if clicked_item is not None:
# edge item
pos = clicked_item.scenePos()
pos.setX(pos.x() + 6)
pos.setY(pos.y() + 6)
self.edge = self.addPath(QPainterPath())
self.edge.setPen(self._edge_pen)
self.start_pos = pos
self.end_pos = self.start_pos
self.update_path()
else:
x = event.scenePos().x()
y = event.scenePos().y()
# port item
start_port = Ellipse()
start_port.setPos(x - 6, y - 6)
start_port.setPen(self._port_pen)
start_port.setBrush(self._port_brush)
start_port.setZValue(10000.0)
self.addItem(start_port)
# edge item
self.edge = self.addPath(QPainterPath())
self.edge.setPen(self._edge_pen)
self.start_pos = event.scenePos()
self.end_pos = self.start_pos
self.update_path()
def mouseMoveEvent(self, event):
if event.buttons() == Qt.MouseButton.LeftButton:
print(f"moving, x : {event.scenePos().x()}, y : {event.scenePos().y()}")
self.end_pos = event.scenePos()
try:
self.update_path()
except AttributeError:
pass
def mouseReleaseEvent(self, event) -> None:
released_item = self.itemAt(event.scenePos(), QTransform())
if event.button() == Qt.MouseButton.LeftButton:
if released_item is not None and released_item.type() != 2:
self.end_pos = released_item.scenePos()
self.end_pos.setX(self.end_pos.x() + 6)
self.end_pos.setY(self.end_pos.y() + 6)
if not self.start_pos.isNull() and not self.end_pos.isNull():
path = QPainterPath()
path.moveTo(self.start_pos.x() - 1, self.start_pos.y() - 1)
path.lineTo(self.end_pos)
self.edge.setPath(path)
else:
x = event.scenePos().x() + 1
y = event.scenePos().y() + 1
end_port = QGraphicsEllipseItem(0, 0, 10, 10)
end_port.setPos(x - 6, y - 6)
end_port.setPen(self._port_pen)
end_port.setBrush(self._port_brush)
end_port.setZValue(10000.0)
self.addItem(end_port)
def update_path(self):
if not self.start_pos.isNull() and not self.end_pos.isNull():
path = QPainterPath()
path.moveTo(self.start_pos.x() - 1, self.start_pos.y() - 1)
path.lineTo(self.end_pos)
self.edge.setPath(path)
class Ellipse(QGraphicsEllipseItem):
def __init__(self):
super().__init__()
self.setRect(0, 0, 10, 10)
self.setAcceptHoverEvents(True)
def hoverEnterEvent(self, event) -> None:
print("hovered")
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec())
What am i doing wrong here, how do i get the hover event to work please ?
Thank you

Related

How can I connect two created ellipses?

I have a code that will paint an ellipse whenever the mouse is clicked.I want to connect the previous ellipse with the new painted ellipse with a line like in the picture here
My code is
import sys
from turtle import pos
from PyQt5.QtCore import Qt, QRectF, QPointF
from PyQt5.QtGui import QPixmap, QTransform, QBrush, QColor, QPen, QPainterPath
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QSizePolicy, QSpacerItem, QGraphicsObject
class MouseBrushObject(QGraphicsObject):
def __init__(self):
QGraphicsObject.__init__(self)
self._size = 10
self._x = 0
self._y = 0
self._pen = QPen(Qt.red, 5)
self._brush = QBrush(Qt.red, 5)
self._px = 0
self._py = 0
def paint(self, painter, option, widget):
#rect = self.boundingRect()
painter.setPen(self._pen)
painter.setBrush(self._brush)
painter.drawEllipse(QPointF(self._x,self._y), 10,10)
self._px = self._x
self._py = self._y
print(QPointF(self._x,self._y))
print(self._x)
print(self._px)
#painter.drawLine(self._x,self._y, 5, 5)
def boundingRect(self):
return QRectF(self._x, self._y, self._size, self._size)
def setSize(self, size):
self._size = size
def setPosition(self, pos):
self._x = pos.x() - pos.x()/2
self._y = pos.y() - pos.y()/2
self.setPos(QPointF(self._x, self._y))
class View(QGraphicsView):
def __init__(self, parent=None):
QGraphicsView.__init__(self, parent=parent)
self.setMouseTracking(True)
self._scene = QGraphicsScene(self)
self.setScene(self._scene)
pixmap = QPixmap(300, 300)
self._scene.addItem(QGraphicsPixmapItem(pixmap))
self._scene.setBackgroundBrush(QBrush(Qt.lightGray))
self.objects = []
def mousePressEvent(self, event):
pos = event.pos()
obj = MouseBrushObject()
obj.setPosition(pos)
self.objects.append(obj)
self._scene.addItem(obj)
class Viewer(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent=parent)
layout = QVBoxLayout()
self.view = View(self)
self.setLayout(layout)
layout.addWidget(self.view)
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.viewer = Viewer(self)
layout = QVBoxLayout()
layout.addWidget(self.viewer)
centralwidget = QWidget(self)
centralwidget.setLayout(layout)
self.setCentralWidget(centralwidget)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
I am thinking of adding drawLine in the paint function, but I can't retrieve the previous x and y because I don't know how to save them. Also, I am thinking of using the qpainterpath, but I don't know how to code it.
(Update)I have already found a way to drawLine using the previous and present ellipse. The points are correct since I have printed it:
but the problem is will not perfectly fit the two ellipses like in this picture:
.
I know that the points from the previous and present points are correct, but the line is not corretly drawn.
import sys
from turtle import pos
from PyQt5.QtCore import Qt, QRectF, QPointF
from PyQt5.QtGui import QPixmap, QTransform, QBrush, QColor, QPen, QPainterPath
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QSizePolicy, QSpacerItem, QGraphicsObject
class MouseBrushObject(QGraphicsObject):
def __init__(self):
QGraphicsObject.__init__(self)
self._size = 10
self._x = 0
self._y = 0
self._pen = QPen(Qt.red, 5)
self._brush = QBrush(Qt.red, 5)
def paint(self, painter, option, widget):
#rect = self.boundingRect()
painter.setPen(self._pen)
painter.setBrush(self._brush)
painter.drawEllipse(QPointF(self._x,self._y), 10,10)
#print("hello")
#print(QPointF(self._x,self._y))
#print(self._x)
#print(self._px)
if not self.prevpos:
print("current xy: "+"("+str(self._x)+","+str(self._y)+")")
else:
print("previous xy: "+"("+str(self._px)+","+str(self._py)+")")
print("new current xy: "+"("+str(self._x)+","+str(self._y)+")")
painter.drawLines(QPointF(self._px,self._py),QPointF(self._x,self._y))
#painter.drawLine(self._px,self._py,self._x,self._y)
def boundingRect(self):
return QRectF(self._x, self._y, self._size, self._size)
def setSize(self, size):
self._size = size
def setPosition(self, pos,prevpos):
self.prevpos = prevpos
#print("meow")
#print(prevpos)
if not prevpos:
self._x = pos.x() - pos.x()/2
self._y = pos.y() - pos.y()/2
#self.setPos(QPointF(self._x, self._y))
else:
self._x = pos.x() - pos.x()/2
self._y = pos.y() - pos.y()/2
self._px = prevpos.x() - prevpos.x()/2
self._py = prevpos.y() - prevpos.y()/2
self.setPos(QPointF(self._x, self._y))
class View(QGraphicsView):
def __init__(self, parent=None):
QGraphicsView.__init__(self, parent=parent)
self.setMouseTracking(True)
self._scene = QGraphicsScene(self)
self.setScene(self._scene)
pixmap = QPixmap(300, 300)
self._scene.addItem(QGraphicsPixmapItem(pixmap))
#self.setTransform(QTransform().scale(1, 1).rotate(0))
self._scene.setBackgroundBrush(QBrush(Qt.lightGray))
#self._brushItem = MouseBrushObject()
self.objects = []
self.posprev = ""
def mousePressEvent(self, event):
pos = event.pos()
if not self.posprev:
obj = MouseBrushObject()
obj.setPosition(pos,self.posprev)
self.objects.append(obj)
self._scene.addItem(obj)
self.posprev = pos
#print(pos)
else:
#print(self.posprev)
#print(pos)
obj = MouseBrushObject()
obj.setPosition(pos,self.posprev)
self.objects.append(obj)
self._scene.addItem(obj)
self.posprev =pos
class Viewer(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent=parent)
layout = QVBoxLayout()
self.view = View(self)
self.setLayout(layout)
layout.addWidget(self.view)
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.viewer = Viewer(self)
layout = QVBoxLayout()
layout.addWidget(self.viewer)
centralwidget = QWidget(self)
centralwidget.setLayout(layout)
self.setCentralWidget(centralwidget)
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())

How to set QGraphicsView 's background Image? [duplicate]

This question already has answers here:
Painting background on QGraphicsView using drawBackground
(1 answer)
How to enable Pan and Zoom in a QGraphicsView
(3 answers)
How to put an image in QGraphicsView's scrollbar area?
(1 answer)
Closed 3 years ago.
I'm wondering how to set QgraphicsView's background Image.
In this painter program, the background is just white color.
I want to load some image(jpg, png, .. whatever) and set it as painter's background.
and after painting it, I want to just save painting (not include background, just what i painted.)
how can i do this? please help me.
import sys
from PyQt5.QtCore import *
from PyQt5.QtCore import Qt
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtWidgets import (QApplication, QCheckBox, QGridLayout, QGroupBox,
QPushButton, QVBoxLayout, QWidget, QSlider)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
class CWidget(QWidget):
def __init__(self):
super().__init__()
# 전체 폼 박스
formbox = QHBoxLayout()
self.setLayout(formbox)
# 좌, 우 레이아웃박스
left = QVBoxLayout()
right = QVBoxLayout()
# 그룹박스2
gb = QGroupBox('펜 설정')
left.addWidget(gb)
grid = QGridLayout()
gb.setLayout(grid)
label = QLabel('펜 색상')
grid.addWidget(label, 1, 0)
self.pencolor = QColor(0, 0, 0)
self.penbtn = QPushButton()
self.penbtn.setStyleSheet('background-color: rgb(0,0,0)')
self.penbtn.clicked.connect(self.showColorDlg)
grid.addWidget(self.penbtn, 1, 1)
label = QLabel('펜 굵기')
grid.addWidget(label, 2, 0)
self.slider = QSlider(Qt.Horizontal)
self.slider.setMinimum(3)
self.slider.setMaximum(21)
self.slider.setValue(5)
self.slider.setFocusPolicy(Qt.StrongFocus)
self.slider.setTickPosition(QSlider.TicksBothSides)
self.slider.setTickInterval(1)
self.slider.setSingleStep(1)
grid.addWidget(self.slider)
# 그룹박스4
gb = QGroupBox('지우개')
left.addWidget(gb)
hbox = QHBoxLayout()
gb.setLayout(hbox)
self.checkbox = QCheckBox('지우개')
self.checkbox.stateChanged.connect(self.checkClicked)
hbox.addWidget(self.checkbox)
left.addStretch(1)
# 우 레이아웃 박스에 그래픽 뷰 추가
self.view = CView(self)
right.addWidget(self.view)
# 전체 폼박스에 좌우 박스 배치
formbox.addLayout(left)
formbox.addLayout(right)
formbox.setStretchFactor(left, 0)
formbox.setStretchFactor(right, 1)
self.setGeometry(100, 100, 800, 500)
def checkClicked(self):
pass
def createExampleGroup(self):
groupBox = QGroupBox("Slider Example")
slider = QSlider(Qt.Horizontal)
slider.setFocusPolicy(Qt.StrongFocus)
slider.setTickPosition(QSlider.TicksBothSides)
slider.setTickInterval(10)
slider.setSingleStep(1)
vbox = QVBoxLayout()
vbox.addWidget(slider)
vbox.addStretch(1)
groupBox.setLayout(vbox)
return groupBox
def showColorDlg(self):
# 색상 대화상자 생성
color = QColorDialog.getColor()
sender = self.sender()
# 색상이 유효한 값이면 참, QFrame에 색 적용
self.pencolor = color
self.penbtn.setStyleSheet('background-color: {}'.format(color.name()))
# QGraphicsView display QGraphicsScene
class CView(QGraphicsView):
def __init__(self, parent):
super().__init__(parent)
self.scene = QGraphicsScene()
self.setScene(self.scene)
self.items = []
self.start = QPointF()
self.end = QPointF()
self.setRenderHint(QPainter.HighQualityAntialiasing)
def moveEvent(self, e):
rect = QRectF(self.rect())
rect.adjust(0, 0, -2, -2)
self.scene.setSceneRect(rect)
def mousePressEvent(self, e):
if e.button() == Qt.LeftButton:
# 시작점 저장
self.start = e.pos()
self.end = e.pos()
def mouseMoveEvent(self, e):
# e.buttons()는 정수형 값을 리턴, e.button()은 move시 Qt.Nobutton 리턴
if e.buttons() & Qt.LeftButton:
self.end = e.pos()
if self.parent().checkbox.isChecked():
pen = QPen(QColor(255, 255, 255), 10)
path = QPainterPath()
path.moveTo(self.start)
path.lineTo(self.end)
self.scene.addPath(path, pen)
self.start = e.pos()
return None
pen = QPen(self.parent().pencolor, self.parent().slider.value())
# Path 이용
path = QPainterPath()
path.moveTo(self.start)
path.lineTo(self.end)
self.scene.addPath(path, pen)
# 시작점을 다시 기존 끝점으로
self.start = e.pos()
def open(self):
fileName, _ = QFileDialog.getOpenFileName(self, "Open File",
QDir.currentPath())
if fileName:
image = QImage(fileName)
if image.isNull():
QMessageBox.information(self, "Image Viewer",
"Cannot load %s." % fileName)
return
self.imageLabel.setPixmap(QPixmap.fromImage(image))
self.scaleFactor = 1.0
self.printAct.setEnabled(True)
self.fitToWindowAct.setEnabled(True)
self.updateActions()
if not self.fitToWindowAct.isChecked():
self.imageLabel.adjustSize()
if __name__ == '__main__':
app = QApplication(sys.argv)
w = CWidget()
w.show()
sys.exit(app.exec_())
You can create an instance of QGraphicsPixmapItem and add it to the scene, here is an example:
import sys
from PyQt5.QtCore import *
from PyQt5.QtCore import Qt
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtWidgets import (QApplication, QCheckBox, QGridLayout, QGroupBox,
QPushButton, QVBoxLayout, QWidget, QSlider)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
class CWidget(QWidget):
def __init__(self):
super().__init__()
# 전체 폼 박스
formbox = QHBoxLayout()
self.setLayout(formbox)
# 좌, 우 레이아웃박스
left = QVBoxLayout()
right = QVBoxLayout()
# 그룹박스2
gb = QGroupBox('펜 설정')
left.addWidget(gb)
grid = QGridLayout()
gb.setLayout(grid)
label = QLabel('펜 색상')
grid.addWidget(label, 1, 0)
self.pencolor = QColor(0, 0, 0)
self.penbtn = QPushButton()
self.penbtn.setStyleSheet('background-color: rgb(0,0,0)')
self.penbtn.clicked.connect(self.showColorDlg)
grid.addWidget(self.penbtn, 1, 1)
label = QLabel('펜 굵기')
grid.addWidget(label, 2, 0)
self.slider = QSlider(Qt.Horizontal)
self.slider.setMinimum(3)
self.slider.setMaximum(21)
self.slider.setValue(5)
self.slider.setFocusPolicy(Qt.StrongFocus)
self.slider.setTickPosition(QSlider.TicksBothSides)
self.slider.setTickInterval(1)
self.slider.setSingleStep(1)
grid.addWidget(self.slider)
# 그룹박스4
gb = QGroupBox('지우개')
left.addWidget(gb)
hbox = QHBoxLayout()
gb.setLayout(hbox)
self.checkbox = QCheckBox('지우개')
self.checkbox.stateChanged.connect(self.checkClicked)
hbox.addWidget(self.checkbox)
left.addStretch(1)
# 우 레이아웃 박스에 그래픽 뷰 추가
self.view = CView(self)
right.addWidget(self.view)
# 전체 폼박스에 좌우 박스 배치
formbox.addLayout(left)
formbox.addLayout(right)
formbox.setStretchFactor(left, 0)
formbox.setStretchFactor(right, 1)
self.setGeometry(100, 100, 800, 500)
def checkClicked(self, state):
self.view.stretch(state)
def createExampleGroup(self):
groupBox = QGroupBox("Slider Example")
slider = QSlider(Qt.Horizontal)
slider.setFocusPolicy(Qt.StrongFocus)
slider.setTickPosition(QSlider.TicksBothSides)
slider.setTickInterval(10)
slider.setSingleStep(1)
vbox = QVBoxLayout()
vbox.addWidget(slider)
vbox.addStretch(1)
groupBox.setLayout(vbox)
return groupBox
def showColorDlg(self):
# 색상 대화상자 생성
color = QColorDialog.getColor()
sender = self.sender()
# 색상이 유효한 값이면 참, QFrame에 색 적용
self.pencolor = color
self.penbtn.setStyleSheet('background-color: {}'.format(color.name()))
# QGraphicsView display QGraphicsScene
class CView(QGraphicsView):
def __init__(self, parent):
super().__init__(parent)
self.scene = QGraphicsScene()
self.setScene(self.scene)
self.items = []
self.start = QPointF()
self.end = QPointF()
self.backgroundImage = None
self.graphicsPixmapItem = None
self.setRenderHint(QPainter.HighQualityAntialiasing)
self.open()
def moveEvent(self, e):
rect = QRectF(self.rect())
rect.adjust(0, 0, -2, -2)
self.scene.setSceneRect(rect)
def mousePressEvent(self, e):
if e.button() == Qt.LeftButton:
# 시작점 저장
self.start = e.pos()
self.end = e.pos()
def mouseMoveEvent(self, e):
# e.buttons()는 정수형 값을 리턴, e.button()은 move시 Qt.Nobutton 리턴
if e.buttons() & Qt.LeftButton:
self.end = e.pos()
if self.parent().checkbox.isChecked():
pen = QPen(QColor(255, 255, 255), 10)
path = QPainterPath()
path.moveTo(self.start)
path.lineTo(self.end)
self.scene.addPath(path, pen)
self.start = e.pos()
return None
pen = QPen(self.parent().pencolor, self.parent().slider.value())
# Path 이용
path = QPainterPath()
path.moveTo(self.start)
path.lineTo(self.end)
self.scene.addPath(path, pen)
# 시작점을 다시 기존 끝점으로
self.start = e.pos()
def stretch(self, state):
self._set_image(state == 2)
def open(self):
fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath(), filter='Images (*.png *.xpm *.jpg)')
if fileName:
image = QImage(fileName)
if image.isNull():
QMessageBox.information(self, "Image Viewer",
"Cannot load %s." % fileName)
return
self.backgroundImage = fileName
self._set_image(False)
def _set_image(self, stretch: bool):
tempImg = QPixmap(self.backgroundImage)
if stretch:
tempImg = tempImg.scaled(self.scene.width(), self.scene.height())
if self.graphicsPixmapItem is not None:
self.scene.removeItem(self.graphicsPixmapItem)
self.graphicsPixmapItem = QGraphicsPixmapItem(tempImg)
self.scene.addItem(self.graphicsPixmapItem)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = CWidget()
w.show()
sys.exit(app.exec_())

How to put a gif in gui using python PyQt5

This program is a trafficlight program but i want to put a gif on the right space of the window that will show a walking man gif when the color is green and a stop gif when in red or yellow so I tried to use QMovie which I get mixed results and still ended up in an error or the gif won't appear at the window can you please help me?
from itertools import cycle
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QTimer,Qt,QPoint
from PyQt5.QtWidgets import QApplication,QMainWindow
from PyQt5.QtGui import QPainter,QColor,QMovie
class TrafficLight(QMainWindow):
def __init__(self,parent = None):
super(TrafficLight, self).__init__(parent)
self.setWindowTitle("TrafficLight ")
self.traffic_light_color1 = cycle(\[
QColor('red'),
QColor('gray'),
QColor('gray')
\])
self.traffic_light_color2 = cycle(\[
QColor('gray'),
QColor('yellow'),
QColor('gray')
\])
self.traffic_light_color3 = cycle(\[
QColor('gray'),
QColor('gray'),
QColor('green')
\])
self._current_color1 = next(self.traffic_light_color1)
self._current_color2 = next(self.traffic_light_color2)
self._current_color3 = next(self.traffic_light_color3)
timer = QTimer(self, timeout=self.change_color)
x = 0
if x == 0 :
self.movie1 = QMovie("Walking-man2[enter image description here][1].gif")
self.movie1.frameChanged.connect(self.repaint)
self.movie1.start()
timer.start(30*100)
x = 1
elif x == 1 :
self.movie1 = QMovie("tenor(1).gif")
self.movie1.frameChanged.connect(self.repaint)
self.movie1.start()
timer.start(10*100)
x = 2
elif x == 2:
self.movie1 = QMovie("tenor(1).gif")
self.movie1.frameChanged.connect(self.repaint)
self.movie1.start()
timer.start(40*100)
x = 0
self.resize(700, 510)
#QtCore.pyqtSlot()
def change_color(self):
self._current_color1 = next(self.traffic_light_color1)
self._current_color2 = next(self.traffic_light_color2)
self._current_color3 = next(self.traffic_light_color3)
self.update()
def paintEvent(self, event):
p1 = QPainter(self)
p1.setBrush(self._current_color1)
p1.setPen(Qt.black)
p1.drawEllipse(QPoint(125, 125), 50, 50)
p2 = QPainter(self)
p2.setBrush(self._current_color2)
p2.setPen(Qt.black)
p2.drawEllipse(QPoint(125, 250),50,50)
p3 = QPainter(self)
p3.setBrush(self._current_color3)
p3.setPen(Qt.black)
p3.drawEllipse(QPoint(125, 375),50,50)
currentFrame = self.movie1.currentPixmap()
frameRect = currentFrame.rect()
frameRect.moveCenter(self.rect().center())
if frameRect.intersects(event.rect()):
painter = QPainter(self)
painter.drawPixmap(frameRect.left(), frameRect.top(), currentFrame)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = TrafficLight()
w.show()
sys.exit(app.exec_())
The logic of changing one state to another can be implemented with a Finite State Machine (FSM), and fortunately Qt implements it using The State Machine Framework:
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
class LightWidget(QtWidgets.QWidget):
def __init__(self, color, parent=None):
super(LightWidget, self).__init__(parent)
self._state = False
self._color = color
self.setFixedSize(150, 150)
#QtCore.pyqtSlot()
def turnOn(self):
self._state = True
self.update()
#QtCore.pyqtSlot()
def turnOff(self):
self._state = False
self.update()
def paintEvent(self, event):
color = self._color if self._state else QtGui.QColor('gray')
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtCore.Qt.black)
painter.setBrush(color)
painter.drawEllipse(self.rect())
class TrafficLightWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TrafficLightWidget, self).__init__(parent)
hlay = QtWidgets.QHBoxLayout(self)
container = QtWidgets.QWidget()
container.setStyleSheet('''background-color : black''')
vlay = QtWidgets.QVBoxLayout(container)
self.m_red = LightWidget(QtGui.QColor("red"))
self.m_yellow = LightWidget(QtGui.QColor("yellow"))
self.m_green = LightWidget(QtGui.QColor("green"))
vlay.addWidget(self.m_red)
vlay.addWidget(self.m_yellow)
vlay.addWidget(self.m_green)
hlay.addWidget(container, alignment=QtCore.Qt.AlignCenter)
self.label = QtWidgets.QLabel("Test", alignment=QtCore.Qt.AlignCenter)
hlay.addWidget(self.label, 1)
red_to_yellow = createLightState(self.m_red, 30*1000, partial(self.change_gif, "gif_red.gif"))
yellow_to_green = createLightState(self.m_yellow, 20*1000, partial(self.change_gif, "gif_yellow.gif"))
green_to_yellow = createLightState(self.m_green, 40*1000, partial(self.change_gif, "gif_green.gif"))
yellow_to_red = createLightState(self.m_yellow, 20*1000, partial(self.change_gif, "gif_yellow.gif"))
red_to_yellow.addTransition(red_to_yellow.finished, yellow_to_green)
yellow_to_green.addTransition(yellow_to_green.finished, green_to_yellow)
green_to_yellow.addTransition(green_to_yellow.finished, yellow_to_red)
yellow_to_red.addTransition(yellow_to_red.finished, red_to_yellow)
machine = QtCore.QStateMachine(self)
machine.addState(red_to_yellow)
machine.addState(yellow_to_green)
machine.addState(green_to_yellow)
machine.addState(yellow_to_red)
machine.setInitialState(red_to_yellow)
machine.start()
#QtCore.pyqtSlot()
def change_gif(self, gif):
last_movie = self.label.movie()
movie = QtGui.QMovie(gif)
self.label.setMovie(movie)
movie.start()
if last_movie is not None:
last_movie.deleteLater()
def createLightState(light, duration, callback):
lightState = QtCore.QState()
timer = QtCore.QTimer(
lightState,
interval=duration,
singleShot=True
)
timing = QtCore.QState(lightState)
timing.entered.connect(light.turnOn)
timing.entered.connect(callback)
timing.entered.connect(timer.start)
timing.exited.connect(light.turnOff)
done = QtCore.QFinalState(lightState)
timing.addTransition(timer.timeout, done)
lightState.setInitialState(timing)
return lightState
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = TrafficLightWidget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Though the answer ain't as fancy as eyllanesc's... You can make the ellipse depend on a variable color, then change the stored variable color and call update(). The gif can be displayed using a label
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QTimer,Qt,QPoint
from PyQt5.QtWidgets import QApplication,QMainWindow
from PyQt5.QtGui import QPainter,QColor,QMovie
class TrafficLight(QtWidgets.QWidget):
def __init__(self,parent = None):
super(TrafficLight, self).__init__(parent)
self.setWindowTitle("TrafficLight ")
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
self.lblGif = QtWidgets.QLabel()
layout.addSpacing(300)
layout.addWidget(self.lblGif)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.changeLight)
self.timer.start(3000)
self.resize(700, 510)
self.x = 0
self.changeLight()
def changeLight(self):
if self.x == 0 :
self.color1 = QColor('red')
self.color2 = QColor('grey')
self.color3 = QColor('grey')
self.loadGif('wait.gif')
self.x = 1
elif self.x == 1 :
self.color1 = QColor('grey')
self.color2 = QColor('yellow')
self.color3 = QColor('grey')
self.loadGif('almost.gif')
self.x = 2
elif self.x == 2:
self.color1 = QColor('grey')
self.color2 = QColor('grey')
self.color3 = QColor('green')
self.loadGif('walk.gif')
self.x = 0
self.update()
def loadGif(self, path):
movie = QtGui.QMovie(path)
self.lblGif.setMovie(movie)
movie.start()
def paintEvent(self, event):
p1 = QPainter(self)
p1.setBrush(self.color1)
p1.setPen(Qt.black)
p1.drawEllipse(QPoint(125, 125), 50, 50)
p1.end()
p2 = QPainter(self)
p2.setBrush(self.color2)
p2.setPen(Qt.black)
p2.drawEllipse(QPoint(125, 250),50,50)
p2.end()
p3 = QPainter(self)
p3.setBrush(self.color3)
p3.setPen(Qt.black)
p3.drawEllipse(QPoint(125, 375),50,50)
p3.end()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = TrafficLight()
w.show()
sys.exit(app.exec_())

PyQt5 Curved QSlider

I've been playing around with customising PyQt widgets using paint events. I've been trying to customise the QSlider widget and have had some success, mostly with CSS styling. However, I'm having difficulty making it curved with a QPainterPath as it always seems flat. Is this something that is beyond the capability of this widget (which would surprise me)? The below is my most recent attempt of many with no success. I tried path points instead of cubicTo() with the same. Can anyone help or point me in the right direction?
from PyQt5 import QtGui, QtWidgets, QtCore
class slider(QtWidgets.QSlider):
def __init__(self, parent=None):
super(slider, self).__init__(parent)
self.parent = parent
self.setMinimum(10)
self.setMaximum(30)
self.setValue(20)
self.setTickPosition(QtWidgets.QSlider.TicksBelow)
self.setTickInterval(5)
def paintEvent(self, event):
qp = QtGui.QPainter()
qp.begin(self)
qp.setRenderHint(QtGui.QPainter.Antialiasing)
self.drawCurve(qp)
qp.end()
def drawCurve(self, qp):
path = QtGui.QPainterPath()
path.moveTo(30, 30)
path.cubicTo(30, 30, 200, 350, 200, 30)
qp.drawPath(path)
To have the sensation of depth it is only to choose the correct colors, for this QPainterPathStroker is also used. On the other hand I added the functionality that the QPainterPath is scaled:
from PyQt5 import QtCore, QtGui, QtWidgets
class PathSlider(QtWidgets.QAbstractSlider):
def __init__(self, path=QtGui.QPainterPath(), *args, **kwargs):
super(PathSlider, self).__init__(*args, **kwargs)
self._path = path
self.stroke_path = self._path
self.scale_path = self._path
def setPath(self, path):
path.translate(-path.boundingRect().topLeft())
self._path = path
self.update()
def path(self):
return self._path
path = QtCore.pyqtProperty(QtGui.QPainterPath, fget=path, fset=setPath)
def paintEvent(self, event):
border = 10
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
sx, sy = (self.rect().width() -2*border)/self.path.boundingRect().width(), \
(self.rect().height() -2*border)/self.path.boundingRect().height()
tr = QtGui.QTransform()
tr.translate(border, border)
tr.scale(sx, sy)
self.scale_path = tr.map(self.path)
stroker = QtGui.QPainterPathStroker()
stroker.setCapStyle(QtCore.Qt.RoundCap)
stroker.setWidth(4)
stroke_path = stroker.createStroke(self.scale_path).simplified()
painter.setPen(QtGui.QPen(self.palette().color(QtGui.QPalette.Shadow), 1))
painter.setBrush(QtGui.QBrush(self.palette().color(QtGui.QPalette.Midlight)))
painter.drawPath(stroke_path)
stroker.setWidth(20)
self.stroke_path = stroker.createStroke(self.scale_path).simplified()
percentage = (self.value() - self.minimum())/(self.maximum() - self.minimum())
highlight_path = QtGui.QPainterPath()
highlight_path.moveTo(self.scale_path.pointAtPercent(0))
n_p = int((self.maximum() + 1 - self.minimum())/self.singleStep())
for i in range(n_p+1):
d = i*percentage/n_p
p = self.scale_path.pointAtPercent(d)
highlight_path.lineTo(p)
stroker.setWidth(3)
new_phighlight_path = stroker.createStroke(highlight_path).simplified()
activeHighlight = self.palette().color(QtGui.QPalette.Highlight)
painter.setPen(activeHighlight)
painter.setBrush(QtGui.QBrush(QtGui.QColor(activeHighlight)))
painter.drawPath(new_phighlight_path)
opt = QtWidgets.QStyleOptionSlider()
r = self.style().subControlRect(QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderHandle, self)
pixmap = QtGui.QPixmap(r.width() + 2*2, r.height() + 2*2)
pixmap.fill(QtCore.Qt.transparent)
r = pixmap.rect().adjusted(2, 2, -2, -2)
pixmap_painter = QtGui.QPainter(pixmap)
pixmap_painter.setRenderHint(QtGui.QPainter.Antialiasing)
pixmap_painter.setPen(QtGui.QPen(self.palette().color(QtGui.QPalette.Shadow), 2))
pixmap_painter.setBrush(self.palette().color(QtGui.QPalette.Base))
pixmap_painter.drawRoundedRect(r, 4, 4)
pixmap_painter.end()
r.moveCenter(p.toPoint())
painter.drawPixmap(r, pixmap)
def minimumSizeHint(self):
return QtCore.QSize(15, 15)
def sizeHint(self):
return QtCore.QSize(336, 336)
def mousePressEvent(self, event):
self.update_pos(event.pos())
super(PathSlider, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
self.update_pos(event.pos())
super(PathSlider, self).mouseMoveEvent(event)
def update_pos(self, point):
if self.stroke_path.contains(point):
n_p = int((self.maximum() + 1 - self.minimum())/self.singleStep())
ls = []
for i in range(n_p):
p = self.scale_path.pointAtPercent(i*1.0/n_p)
ls.append(QtCore.QLineF(point, p).length())
j = ls.index(min(ls))
val = int(j*(self.maximum() + 1 - self.minimum())/n_p)
self.setValue(val)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
s1 = PathSlider(minimum=0, maximum=100)
s2 = PathSlider(minimum=0, maximum=100)
s = QtWidgets.QSlider(minimum=0, maximum=100)
s.valueChanged.connect(s1.setValue)
s.valueChanged.connect(s2.setValue)
s1.valueChanged.connect(s.setValue)
s2.valueChanged.connect(s.setValue)
c1 = QtCore.QPointF(5, -15)
c2 = QtCore.QPointF(220, -15)
path = QtGui.QPainterPath(QtCore.QPointF(5, 100))
path.cubicTo(c1, c2, QtCore.QPointF(235, 100))
s1.setPath(path)
c1 = QtCore.QPointF(5, 15)
c2 = QtCore.QPointF(220, 15)
path = QtGui.QPainterPath(QtCore.QPointF(5, -100))
path.cubicTo(c1, c2, QtCore.QPointF(235, -100))
s2.setPath(path)
lay = QtWidgets.QHBoxLayout(w)
lay.addWidget(s1)
lay.addWidget(s2)
lay.addWidget(s)
w.show()
sys.exit(app.exec_())

How to subtract QPainterPaths, QPolygon?

I'm trying to understand how path1.subtracted(path2) works.
I have path1 and path2:
And I'm getting path3 using path3=path1.subtracted(path2).
Why I'm not getting a path I want? Image:
Here is the code:
from PyQt5.QtCore import QPointF
from PyQt5.QtCore import QRectF, Qt
from PyQt5.QtGui import QPainterPath, QPen
from PyQt5.QtGui import QPolygonF
from PyQt5.QtWidgets import QApplication, QGraphicsScene, \
QGraphicsView, QPushButton, QWidget, \
QVBoxLayout, QGraphicsItem, QGraphicsPathItem, QGraphicsRectItem
class Window(QWidget):
scene = None
def __init__(self):
QWidget.__init__(self)
self.view = View(self)
self.button = QPushButton('Clear View', self)
self.button.clicked.connect(self.handleClearView)
layout = QVBoxLayout(self)
layout.addWidget(self.view)
layout.addWidget(self.button)
def handleClearView(self):
self.view.scene.clear()
class View(QGraphicsView):
def __init__(self, parent):
self.scribing = False
self.erasing = False
QGraphicsView.__init__(self, parent)
self.scene = QGraphicsScene()
self.setScene(self.scene)
def resizeEvent(self, QResizeEvent):
self.setSceneRect(QRectF(self.viewport().rect()))
def mousePressEvent(self, event):
if event.buttons() == Qt.LeftButton:
self.scribing = True
self.path1 = QPainterPath()
self.path2 = QPainterPath()
self.polygon1 = QPolygonF()
self.polygon1.append(QPointF(100,100))
self.polygon1.append(QPointF(100, 300))
self.polygon1.append(QPointF(300, 300))
self.polygon1.append(QPointF(300, 100))
self.polygon2 = QPolygonF()
self.polygon2.append(QPointF(300,100))
self.polygon2.append(QPointF(300, 300))
self.polygon2.append(QPointF(100, 300))
self.path1.addPolygon(self.polygon1)
self.path2.addPolygon(self.polygon2)
path3 = self.path1.subtracted(self.path2)
# self.scene.addPath(self.path1, QPen(Qt.blue))
# self.scene.addPath(self.path2, QPen(Qt.green))
self.scene.addPath(path3, QPen(Qt.red))
if event.buttons() == Qt.RightButton:
self.erasing = True
def mouseMoveEvent(self, event):
if (event.buttons() & Qt.LeftButton) and self.scribing:
if self.free_draw_item:
pass
if event.buttons() & Qt.RightButton and self.erasing:
pass
def mouseReleaseEvent(self, event):
self.scribing = False
self.erasing = False
# if self.eraser_item != None:
# self.scene.removeItem(self.eraser_item)
# if self.free_draw_item != None:
# self.free_draw_item.setSelected(True)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
In this sample I'm working with QPolygonF. Also I've tried to create p1=QPainterPath(), p2=QPainterPath() and subtracted to get p3. But, without success, getting the same result.
QpainterPath.subtracted() doesn't subtract path elements but path areas,
see documentation
same effect if QpainterPath::operator-() is used:
# path3 = self.path1.subtracted(self.path2)
path3 = self.path1 – self.path2
You can identify the elements of a path by something like this
c = path3.elementCount()
for i in range(c):
e = path3.elementAt(i)
print('Element-nr.: ', i, 'Type: ', e.type, 'x: ', e.x, 'y: ', e.y) # type: 0 = MoveTo, 1 = LineTo
I think, you have to write an own method, which creates path3 from the elements of path1 and path2.

Categories

Resources