Drawing with a brush - python

I need help with the implementation of the brush on PyQt5
I already have some event code for the mouse:
def mousePressEvent(self, event):
if event.button() and event.button() == Qt.LeftButton:
self.lastPoint = event.pos()
self.scribbling = True
def mouseMoveEvent(self, event):
if (event.buttons() & Qt.LeftButton) and self.scribbling:
self.drawLineTo(event.pos())
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton and self.scribbling:
self.drawLineTo(event.pos())
self.scribbling = False
Inside of which the brush drawing function is declared:
def drawLineTo(self, endPoint):
painter = QPainter(self.image)
painter.setPen(QPen(self.myPenColor, self.myPenWidth, Qt.SolidLine,
Qt.RoundCap, Qt.RoundJoin))
painter.drawLine(self.lastPoint, endPoint)
self.modified = True
rad = self.myPenWidth / 2 + 2
self.update(QRect(self.lastPoint, endPoint).normalized().adjusted(-rad, -rad, +rad, +rad))
self.lastPoint = QPoint(endPoint)
But the main problem is that this function is declared in the events themselves. Therefore, drawing goes immediately, and I can not add other tools. Because together with them the "pencil" will constantly be drawn. But I need to somehow pull out the function from there and assign it to the corresponding button. To include only by clicking on this button.
Let's say I have some toolbar:
toolbar = self.addToolBar('Инструменты')
toolbar.addAction(self.pen)
For which there is an action:
self.pen = QAction(QIcon('Image/pen.png'), 'Pencil', self)
self.pen.triggered.connect(self. )
How would I do so in the "triggered.connect" assign the drawing function, and that it works only when the button is clicked.
Maybe there are some bonds for this, like in tkinter, in the similarity:
def draw_pen(self):
self.parent.config(cursor="arrow")
self.parent.unbind("<Button-1>")
self.parent.unbind("<Motion>")
self.parent.bind("<ButtonPress-1>", self.button_press)
self.parent.bind('<B1-Motion>', self.draw_pencil_move)
self.parent.bind('<ButtonRelease-1>', self.draw_pencil_release)
And, in the end, I just assigned this function to the button, and everything worked fine
I will be very grateful for answers and especially for examples of solving problems or examples of brushes that are not declared in the events
P.S. I apologize for my English, if something is wrong с:

Try it:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyScribbling(QMainWindow):
def __init__(self):
super().__init__()
self.penOn = QAction(QIcon('Image/ok.png'), 'Включить рисование', self)
self.penOn.triggered.connect(self.drawingOn)
self.penOff = QAction(QIcon('Image/exit.png'), 'ВЫКЛЮЧИТЬ рисование', self)
self.penOff.triggered.connect(self.drawingOff)
toolbar = self.addToolBar('Инструменты')
toolbar.addAction(self.penOn)
toolbar.addAction(self.penOff)
self.scribbling = False
self.myPenColor = Qt.red # +
self.myPenWidth = 3 # +
self.lastPoint = QPoint()
self.image = QPixmap("Image/picture.png")
self.setFixedSize(600, 600)
self.resize(self.image.width(), self.image.height())
self.show()
# +++
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(self.rect(), self.image)
def mousePressEvent(self, event):
# if event.button() and event.button() == Qt.LeftButton: # -
if (event.button() == Qt.LeftButton) and self.scribbling: # +
self.lastPoint = event.pos()
# self.scribbling = True # -
def mouseMoveEvent(self, event):
if (event.buttons() & Qt.LeftButton) and self.scribbling:
# self.drawLineTo(event.pos()) # -
# +++
painter = QPainter(self.image)
painter.setPen(QPen(self.myPenColor, self.myPenWidth,
Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
painter.drawLine(self.lastPoint, event.pos())
# self.modified = True # ?
self.lastPoint = event.pos()
self.update()
# ?
#rad = self.myPenWidth / 2 + 2
#self.update(QRect(self.lastPoint, event.pos()).normalized().adjusted(-rad, -rad, +rad, +rad))
#self.lastPoint = QPoint(event.pos())
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton and self.scribbling:
#self.drawLineTo(event.pos())
#self.scribbling = False
pass
# Перенес в mouseMoveEvent
# def drawLineTo(self, endPoint):
# painter = QPainter(self.image)
# painter.setPen(QPen(self.myPenColor, self.myPenWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
# painter.drawLine(self.lastPoint, endPoint)
# self.modified = True
# rad = self.myPenWidth / 2 + 2
# self.update(QRect(self.lastPoint, endPoint).normalized().adjusted(-rad, -rad, +rad, +rad))
# self.lastPoint = QPoint(endPoint)
# +++
def drawingOn(self):
self.scribbling = True
# +++
def drawingOff(self):
self.scribbling = False
if __name__ == '__main__':
app = QApplication(sys.argv)
mainMenu = MyScribbling()
sys.exit(app.exec_())

Related

Correct handling of KeyEvent in Pyqt5, problem with catching KeyPressEvent

I have KeyEvent(self, event) function and transfer event.key() to function in module MoveKeyboard but only catch KeyReleaseEvent. I need to handle the release and continuous holding of the button and execute appropriate action . I tested KeyEvent(self, event) and in console output is only: 1: Release.
Joy, rviz, ..., move_keyboard have PyQt5.QtWidgets.QWidget class.
Main window code:
class MainWindow(PyQt5.QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__(None)
self.title = 'Robot teleoperation'
self.left = 10
self.top = 10
self.width = 1920
self.height = 1080
rospy.init_node("gui_node")
#self.joy = Joystick(maxDistance=50,MinimumSize=100,EclipseX=-20,EclipseY=40)
#self.rviz = Rviz()
#self.arm_position = BaseArmPosition()
#self.laser_position = LaserPosition()
#self.move_slider = MoveSlider()
#self.arm_slider = ArmSlider()
self.move_keyboard = MoveKeyboard()
self.initUI()
def initUI(self):
self.central_widget = PyQt5.QtWidgets.QWidget()
self.setCentralWidget(self.central_widget)
grid = PyQt5.QtWidgets.QGridLayout(self.centralWidget())
#grid.addWidget(self.rviz, 0, 0)
#grid.addWidget(self.joy, 0, 1)
#grid.addWidget(self.arm_slider,0,2)
#grid.addWidget(self.arm_position, 1, 0)
#grid.addWidget(self.move_slider, 1,1)
grid.addWidget(self.move_keyboard,0,0)
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.show()
def keyPressEvent(self, event):
print("1: Press")
self.move_keyboard.PressEvent(event)
if event.key() == PyQt5.QtCore.Qt.Key_Control:
print("2: Press")
self.move_keyboard.PressEvent(event)
return super(MainWindow, self).keyPressEvent(event)
def keyReleaseEvent(self, event):
print("1: Release")
self.move_keyboard.ReleaseEvent(event)
if event.key() == PyQt5.QtCore.Qt.Key_Control:
print("2: Release")
self.move_keyboard.ReleaseEvent(event)
return super(MainWindow, self).keyReleaseEvent(event)
Class MoveKeyoard:
import PyQt5
import rospy
from geometry_msgs.msg import Twist
class MoveKeyboard(PyQt5.QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(MoveKeyboard, self).__init__(*args, **kwargs)
self.Up = PyQt5.QtWidgets.QLabel("Up(W)")
self.Left = PyQt5.QtWidgets.QLabel("Left(A)")
self.Right = PyQt5.QtWidgets.QLabel("Right(D)")
self.Down = PyQt5.QtWidgets.QLabel("Down(S)")
self.LinearValue = PyQt5.QtWidgets.QDoubleSpinBox(self)
self.AngularValue = PyQt5.QtWidgets.QDoubleSpinBox(self)
self.label_linear = PyQt5.QtWidgets.QLabel("Linear speed")
self.label_angular = PyQt5.QtWidgets.QLabel("Angular speed")
self.initUI()
def initUI(self):
self.LinearValue.setMaximum(1.00)
self.LinearValue.setMinimum(0.00)
self.LinearValue.setSingleStep(0.01)
self.AngularValue.setMaximum(1.00)
self.AngularValue.setMinimum(0.00)
self.AngularValue.setSingleStep(0.01)
layout = PyQt5.QtWidgets.QGridLayout(self)
layout.addWidget(self.label_linear,0,0)
layout.addWidget(self.LinearValue,0,1)
layout.addWidget(self.label_angular,1,0)
layout.addWidget(self.AngularValue,1,1)
layout.addWidget(self.Up,2,1)
layout.addWidget(self.Left,3,0)
layout.addWidget(self.Down,3,1)
layout.addWidget(self.Right,3,2)
self.pub = rospy.Publisher('cmd_vel', Twist, queue_size = 1)
def PressEvent(self, e):
if e.key() == PyQt5.QtCore.Qt.Key_W:
self.Up.setStyleSheet('color: red')
self.do("W")
if e.key() == PyQt5.QtCore.Qt.Key_S:
self.Down.setStyleSheet('color: red')
self.do("S")
if e.key() == PyQt5.QtCore.Qt.Key_A:
self.Left.setStyleSheet('color: red')
self.do("A")
if e.key() == PyQt5.QtCore.Qt.Key_D:
self.Right.setStyleSheet('color: red')
self.do("D")
def ReleaseEvent(self, e):
if e.key() == PyQt5.QtCore.Qt.Key_W:
self.Up.setStyleSheet('color: black')
self.do("---W")
if e.key() == PyQt5.QtCore.Qt.Key_S:
self.Down.setStyleSheet('color: black')
self.do("---S")
if e.key() == PyQt5.QtCore.Qt.Key_A:
self.Left.setStyleSheet('color: black')
self.do("---A")
if e.key() == PyQt5.QtCore.Qt.Key_D:
self.Right.setStyleSheet('color: black')
self.do("---D")
def do(self,str_):
print(str_)
If a widget consumes the QKeyEvent event (event.accept()) then that event will not be propagated, in this case QDoubleSpinBox consumes the events they use as a Qt.ControlModifier modifier so that other widgets will not receive the press, only the release.
So the solution in this case is to prevent the QDoubleSpinBox from consuming the combination Ctrl+W, Ctrl+S, Ctrl+A and Ctrl+D.
On the other hand I do not need the window to handle these events, I think it is better for the widget itself to handle them.
from PyQt5 import QtCore, QtWidgets
class DoubleSpinBox(QtWidgets.QDoubleSpinBox):
def keyPressEvent(self, event):
if event.modifiers() == QtCore.Qt.ControlModifier and event.key() in (
QtCore.Qt.Key_W,
QtCore.Qt.Key_S,
QtCore.Qt.Key_A,
QtCore.Qt.Key_D,
):
event.ignore()
else:
super(DoubleSpinBox, self).keyPressEvent(event)
class MoveKeyboard(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MoveKeyboard, self).__init__(parent)
self.Up = QtWidgets.QLabel("Up(W)")
self.Left = QtWidgets.QLabel("Left(A)")
self.Right = QtWidgets.QLabel("Right(D)")
self.Down = QtWidgets.QLabel("Down(S)")
self.LinearValue = DoubleSpinBox()
self.AngularValue = DoubleSpinBox()
self.label_linear = QtWidgets.QLabel("Linear speed")
self.label_angular = QtWidgets.QLabel("Angular speed")
self.initUI()
def initUI(self):
self.LinearValue.setMaximum(1.00)
self.LinearValue.setMinimum(0.00)
self.LinearValue.setSingleStep(0.01)
self.AngularValue.setMaximum(1.00)
self.AngularValue.setMinimum(0.00)
self.AngularValue.setSingleStep(0.01)
layout = QtWidgets.QGridLayout(self)
layout.addWidget(self.label_linear, 0, 0)
layout.addWidget(self.LinearValue, 0, 1)
layout.addWidget(self.label_angular, 1, 0)
layout.addWidget(self.AngularValue, 1, 1)
layout.addWidget(self.Up, 2, 1)
layout.addWidget(self.Left, 3, 0)
layout.addWidget(self.Down, 3, 1)
layout.addWidget(self.Right, 3, 2)
def get_widget_by_key(self, key):
d = {
QtCore.Qt.Key_W: ("W", self.Up),
QtCore.Qt.Key_S: ("S", self.Down),
QtCore.Qt.Key_A: ("A", self.Left),
QtCore.Qt.Key_D: ("D", self.Right),
}
return d.get(key, ("", None))
def keyPressEvent(self, event):
if event.modifiers() == QtCore.Qt.ControlModifier and not event.isAutoRepeat():
letter, widget = self.get_widget_by_key(event.key())
if widget is not None:
widget.setStyleSheet("color: red")
print(letter)
super(MoveKeyboard, self).keyPressEvent(event)
def keyReleaseEvent(self, event):
if event.modifiers() == QtCore.Qt.ControlModifier and not event.isAutoRepeat():
letter, widget = self.get_widget_by_key(event.key())
if widget is not None:
widget.setStyleSheet("color: black")
print("--{}".format(letter))
super(MoveKeyboard, self).keyReleaseEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__(None)
self.title = "Robot teleoperation"
self.move_keyboard = MoveKeyboard()
self.initUI()
def initUI(self):
self.central_widget = QtWidgets.QWidget()
self.setCentralWidget(self.central_widget)
grid = QtWidgets.QGridLayout(self.centralWidget())
grid.addWidget(self.move_keyboard, 0, 0)
self.setWindowTitle(self.title)
self.setGeometry(10, 10, 1920, 1080)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

Why can't I use the mouse Event(mouseMoveEvent, mouseReleaseEvent) on the graphicsview?

I want to make cropping image tool by using python and pyqt5
Below code, I can't use the mouseMoveEvent and mouseReleaseEvent on
the graphic view.
But, I only can use the mousePressEvent on the graphicview. I checked the interactive about QGraphicView properties.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PIL import Image
from PyQt5 import uic
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
IS_RESULT = False
CROP_UI = uic.loadUiType("Image_crop_screen.ui")[0]
BRING_IN_IMG_ROUTE = "C:/Users/yoon/Desktop/test/test.jpg"
class MainScreen(QMainWindow, CROP_UI):
def __init__(self):
super().__init__()
self.setupUi(self)
# Graphic Screen set
self.img = QGraphicsPixmapItem(QPixmap(BRING_IN_IMG_ROUTE))
self.scene = QGraphicsScene()
self.scene.addItem(self.img)
self.graphicsView.setScene(self.scene)
self.graphicsView.pencolor = QColor(240, 240, 240)
self.graphicsView.brushcolor = QColor(255, 255, 255, 0)
QGV = QGraphicsView()
self.items = []
# Full Screen set size
_WIDTH_ADD = 25
_HEIGHT_ADD = 25
self.setGeometry(0, 0, 640 + _WIDTH_ADD, 500 + _HEIGHT_ADD)
def moveEvent(self, e):
rect = QRectF(self.rect())
rect.adjust(0, 0, 0, 0) # 창 스크롤 바 없애기 위해서 일부 크기 작게 설정
self.scene.setSceneRect(rect)
# Draw rectangular while moving mouse
def mouseMoveEvent(self, e):
# e.buttons()는 정수형 값을 리턴, e.button()은 move시 Qt.Nobutton 리턴
print(Qt.LeftButton)
print(e.buttons())
print(e)
if e.buttons() & Qt.LeftButton:
self.end = e.pos()
pen = QPen(self.parent().pencolor)
brush = QBrush(self.parent().brushcolor)
pen.setWidth(3)
# 장면에 그려진 이전 선을 제거
if len(self.items) > 0:
self.scene.removeItem(self.items[-1])
del (self.items[-1])
rect = QRectF(self.start, self.end)
self.items.append(self.scene.addRect(rect, pen, brush))
def mousePressEvent(self, e):
if e.button() == Qt.LeftButton:
# 시작점 저장
self.start = e.pos()
self.end = e.pos()
print(Qt.LeftButton)
print(e.buttons())
print(e)
def mouseReleaseEvent(self, e):
print("aaa2")
if e.button() == Qt.LeftButton:
global IS_RESULT
print("ttt2")
pen = QPen(self.parent().pencolor)
brush = QBrush(self.parent().brushcolor)
self.items.clear()
rect = QRectF(self.start, self.end)
self.scene.addRect(rect, pen, brush)
print("(" + str(self.start.x()) + ", " + str(self.start.y()) + "), (" + str(self.end.x()) + ", " + str(
self.end.y()) + ")")
area = (self.start.x(), self.start.y(), self.end.x(), self.end.y())
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainScreen()
w.show()
sys.exit(app.exec_())
If you want to listen to the events of the QGraphicsView you should not override the events of another widget since some events will not be transmitted. In this case it is better to use an event filter that tracks the mouse, in this case the events of the viewport() of the QGraphicsView should be monitored.
In the following example I show how to create rectangles with the mouse.
class MainScreen(QMainWindow, CROP_UI):
def __init__(self):
super().__init__()
self.setupUi(self)
# Graphic Screen set
self.img = QGraphicsPixmapItem(QPixmap(BRING_IN_IMG_ROUTE))
self.scene = QGraphicsScene()
self.scene.addItem(self.img)
self.graphicsView.setScene(self.scene)
# Full Screen set size
_WIDTH_ADD = 25
_HEIGHT_ADD = 25
self.setGeometry(0, 0, 640 + _WIDTH_ADD, 500 + _HEIGHT_ADD)
self.graphicsView.viewport().installEventFilter(self)
self.current_item = None
self.start_pos = QPointF()
self.end_pos = QPointF()
def eventFilter(self, o, e):
if self.graphicsView.viewport() is o:
if e.type() == QEvent.MouseButtonPress:
if e.buttons() & Qt.LeftButton:
print("press")
self.start_pos = self.end_pos = self.graphicsView.mapToScene(
e.pos()
)
pen = QPen(QColor(240, 240, 240))
pen.setWidth(3)
brush = QBrush(QColor(100, 255, 100, 100))
self.current_item = self.scene.addRect(QRectF(), pen, brush)
self._update_item()
elif e.type() == QEvent.MouseMove:
if e.buttons() & Qt.LeftButton and self.current_item is not None:
print("move")
self.end_pos = self.graphicsView.mapToScene(e.pos())
self._update_item()
elif e.type() == QEvent.MouseButtonRelease:
print("release")
self.end_pos = self.graphicsView.mapToScene(e.pos())
self._update_item()
self.current_item = None
return super().eventFilter(o, e)
def _update_item(self):
if self.current_item is not None:
self.current_item.setRect(QRectF(self.start_pos, self.end_pos).normalized())
You have to create a class with type QGraphicView in which you will use the mouse events
class MyGraphicView(QGraphicsView):
def __init__():
super.__init__(self)
self.myScene = QGraphicsScene()
self.setScene(self.myScene)
# the rest code
def mousePressEvent(self, event):
...
def mouseMoveEvent(self, event):
...
def mouseReleaseEvent(self, event):
...

Removing dotted rectangle on selected item

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...
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:
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

QGraphicsEllipseItem paint event being clipped

I have two issues here.
When i paint the ellipse it appears the edges of the ellipse are not being painted white during the hover event. How can i fix this both when the dot is it's regular size and when it's being hovered over?
When the user hovers over the dot I want the dot's radius to increase by 2, however when i do that, it currently does not scale from the center of the dot. How can i adjust so it increases it's size based on the center point of the dot?
import sys
from PySide.QtGui import *
from PySide.QtCore import *
import random
class MyGraphicsView(QGraphicsView):
def __init__(self):
super(MyGraphicsView, self).__init__()
self.setDragMode(QGraphicsView.RubberBandDrag)
self._isPanning = False
self._mousePressed = False
# self.setBackgroundBrush(QImage("C:/Users/jmartini/Desktop/Temp/images/flag_0140.jpg"))
self.setCacheMode(QGraphicsView.CacheBackground)
self.setHorizontalScrollBarPolicy( Qt.ScrollBarAlwaysOff )
self.setVerticalScrollBarPolicy( Qt.ScrollBarAlwaysOff )
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__()
self.setBackgroundBrush(QBrush(QColor(50,50,50)))
class EllipseItem(QGraphicsEllipseItem):
def __init__(self):
super(EllipseItem, self).__init__()
self.setAcceptHoverEvents(True)
self.hover = False
def paint(self, painter, option, widget):
painter.setRenderHints( QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing, True )
painter.setBrush(QBrush(QColor(170,170,170,255)))
if self.isSelected():
painter.setPen(QPen(QColor(255,255,255), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
else:
painter.setPen(QPen(QColor(30,30,30), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
self.setRect(-16, -16, 16, 16)
if self.hover:
painter.setPen(QPen(QColor(255,255,255), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
self.setRect(-18, -18, 18, 18)
painter.drawEllipse(self.rect())
def hoverEnterEvent(self, event):
self.hover = True
self.update()
super(self.__class__, self).hoverEnterEvent(event)
def hoverLeaveEvent(self, event):
self.hover = False
self.update()
super(self.__class__, self).hoverEnterEvent(event)
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
self.setWindowTitle("Test")
self.resize(800,600)
self.gv = MyGraphicsView()
self.gv.setScene(MyGraphicsScene(self))
lay_main = QVBoxLayout()
lay_main.addWidget(self.gv)
widget_main = QWidget()
widget_main.setLayout(lay_main)
self.setCentralWidget(widget_main)
self.populate()
def populate(self):
scene = self.gv.scene()
item = EllipseItem()
item.setFlag( QGraphicsItem.ItemIsSelectable )
scene.addItem(item)
def main():
app = QApplication(sys.argv)
ex = MyMainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I'm not quite sure of what you exactly want (maybe it's my english). From running your code I see that on hover the edges get cut from the boxes and that the two circles are not centered on the same point.
Well, the centering problem is due to the use of the setRect method on the paint overload of your EllipseItem class. For the hovering case (I'm disregarding the selected item logic of your code), you are setting rectangles that actually have different centers. For solving this issue you could use QPainter.drawEllipse(center, rx, ry), docs.
The edges issue could be fixed noting that painted edges are not considered on the rectangle setted by setRect as posted here. To be honest I'm not quite sure why the smaller ellipse fits the rectangle and the larger one doesnt.
Anyway, you can try doing something like this:
def paint(self, painter, option, widget):
painter.setRenderHints( QPainter.Antialiasing | QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform | QPainter.HighQualityAntialiasing, True )
painter.setBrush(QBrush(QColor(170,170,170,255)))
# if self.isSelected():
# painter.setPen(QPen(QColor(255,255,255), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
# else:
# painter.setPen(QPen(QColor(30,30,30), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
#self.setRect(-16, -16, 16, 16)
center = QPointF(0,0)
if self.hover:
painter.setPen(QPen(QColor(255,255,255), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
# self.setRect(-10,-10, 20, 20) # this one wont fit the ellipse
self.setRect(-11, -11, 22, 22)
# plus 2
painter.drawEllipse(center, 10, 10)
else:
painter.setPen(QPen(QColor(30,30,30), 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
self.setRect(-8, -8, 16, 16) # but this one does
painter.drawEllipse(center, 8, 8)
Clearly you can center your ellipses in multiple ways, the code above is just one way of doing it.
Hope it helps.

drag and drop a Tab from a QtabBar to other QtabBar in a splitted Widget PyQt Qt

How could I archive this:
- I need to drag and drop a tab from its tabBar to other tabBar in a splitted widget?
I already subclass the QtabBar and implement the drag and drop events, i already can drag it with the right pixmap and etc, and also i can drop it into the same tabBar, but not in the other one ..
got this error in the output telling me that im not providing the right arguments, here is the code, that i simplified for make it and example, and plus a .JPG of the window.
class EsceneTest(qg.QMainWindow):
def __init__(self,parent=getMayaWindow()):
super(EsceneTest,self).__init__(parent)
#---------------------------------------------------------#
#check for open Window first
winName = windowTitle
if cmds.window(winName, exists =1):
cmds.deleteUI(winName, wnd=True)
self.setAttribute(qc.Qt.WA_DeleteOnClose)
self._initUI()
def _initUI(self):
self.setObjectName(windowObject)
self.setWindowTitle(windowTitle)
self.setMinimumWidth(450)
self.setMinimumHeight(500)
self.resize(1080, 800) # re-size the window
centralWidget = qg.QWidget()
centralWidget.setObjectName('centralWidget')
self.setCentralWidget(centralWidget)
central_layout = qg.QVBoxLayout(centralWidget)
######################
# tab container
#
self.tabWidget = qg.QTabWidget()
self.tabWidget.setAcceptDrops(True)
self.tab_layout = qg.QVBoxLayout(self.tabWidget)
central_layout.addWidget(self.tabWidget)
#######################
# TabBar
#
custom_tabbar = ColtabBar()
self.tabWidget.setTabBar(custom_tabbar)
#######################
# ViewportTab
#
tabCentral_wdg = qg.QWidget()
self.top_lyt = qg.QVBoxLayout(tabCentral_wdg)
self.tab_layout.addLayout(self.top_lyt)
fixedHBox_lyt = qg.QHBoxLayout()
self.top_lyt.addLayout(fixedHBox_lyt)
self.tabWidget.addTab(tabCentral_wdg,'- Viewport')
#######################
# Example ExtraTab
#
tabTwo_wdg = qg.QWidget()
tabTwo_wdg_lyt = qg.QHBoxLayout(tabTwo_wdg)
self.tab_layout.addLayout(tabTwo_wdg_lyt)
label = qg.QLabel(' -- This is an example -- ')
label.setStyleSheet("""
background : qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgb(53, 57, 60), stop:1 rgb(33, 34, 36));
border-style : none;
font-size: 40px;
font-family: Calibri;
color : rgb(200,200,100);
""")
label.setAlignment(qc.Qt.AlignVCenter | qc.Qt.AlignHCenter )
tabTwo_wdg_lyt.addWidget(label)
tab_panel_lyt = qg.QVBoxLayout(label)
self.tabWidget.addTab(tabTwo_wdg,'- ExtraExample')
############################
# Q Splitter Widget to insert the dragged Tabs
#
split = qg.QSplitter(qc.Qt.Orientation.Vertical, self)
central_layout.addWidget(split)
tab_splitted = qg.QTabWidget()
split.setLayout(qg.QVBoxLayout())
split.insertWidget(0,tab_splitted)
tabBar_2 = ColtabBar()
tab_splitted.setTabBar(tabBar_2)
tabBar_2.addTab('- Insert-Here')
#---------------------------------------------------------------------------------------------#
class ColtabBar(qg.QTabBar):
def __init__(self):
super(ColtabBar, self).__init__()
self.indexTab = None
self.setAcceptDrops(True)
##################################
# Events
def mouseMoveEvent(self, e):
if e.buttons() != qc.Qt.MiddleButton:
return
globalPos = self.mapToGlobal(e.pos())
posInTab = self.mapFromGlobal(globalPos)
self.indexTab = self.tabAt(e.pos())
tabRect = self.tabRect(self.indexTab)
pixmap = qg.QPixmap(tabRect.size())
self.render(pixmap,qc.QPoint(),qg.QRegion(tabRect))
mimeData = qc.QMimeData()
drag = qg.QDrag(self)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
cursor = qg.QCursor(qc.Qt.OpenHandCursor)
drag.setHotSpot(e.pos() - posInTab)
drag.setDragCursor(cursor.pixmap(),qc.Qt.MoveAction)
dropAction = drag.exec_(qc.Qt.MoveAction)
def mousePressEvent(self, e):
#super(qg.QWidget).mousePressEvent(e)
if e.button() == qc.Qt.RightButton:
print('press')
if e.button() == qc.Qt.LeftButton:
globalPos = self.mapToGlobal(e.pos())
posInTab = self.mapFromGlobal(globalPos)
self.indexTab = self.tabAt(e.pos())
self.setCurrentIndex(self.indexTab)
def dragEnterEvent(self, e):
e.accept()
def dropEvent(self, e):
e.setDropAction(qc.Qt.MoveAction)
e.accept()
self.insertTab(self.indexTab, self.tabText(self.indexTab))
self.removeTab(self.indexTab)
the ColtabBar is the subclass where im doing the drag and drop events.
IMAGE - >
After many hours and have eaten many manyyyy pages of Qt today over the web, I did it in my way, now I can drag and drop tabs from one tabBar to the other and vice-versa and not just from selection the current tab, i could select every tab that I want in my tab bar and will show me the pixmap of the little tab while dragging...
Here is the code:
** EDITED **
I made it more bullet proof, I had a bug when I was using more than 2 tabs with the index, now is working better, and when I drop it in the same widget it return the event and not execute the code, plus the hovering tabs select with the right mouse button as well .. I hope this can help anybody in the future.
TABINDEX = int()
def getTabIndex(index):
global TABINDEX
if index == -1 or index == TABINDEX:
return
TABINDEX = index
print (TABINDEX)
return TABINDEX
class ColtTab(qg.QTabWidget):
def __init__(self):
super(ColtTab,self).__init__()
self.setAcceptDrops(True)
self.tabBar = self.tabBar()
self.tabBar.setMouseTracking(True)
self.setDocumentMode(True)
self.indexTab = int()
self.setMovable(True)
self.setStyleSheet(style_sheet_file)
# test for hovering and selecting tabs automatic while mouser over then - not working for now...
def eventFilter(self, obj, event):
if obj == self.tabBar:
if event.type() == qc.QEvent.MouseMove:
index=self.tabBar.tabAt(event.pos())
self.tabBar.setCurrentIndex (index)
return True
else:
return
else:
return
##################################
# Events
#
def mouseMoveEvent(self, e):
if e.buttons() != qc.Qt.MiddleButton:
return
globalPos = self.mapToGlobal(e.pos())
#print(globalPos)
tabBar = self.tabBar
#print(tabBar)
posInTab = tabBar.mapFromGlobal(globalPos)
#print(posInTab)
self.indexTab = tabBar.tabAt(e.pos())
#print(self.indexTab)
tabRect = tabBar.tabRect(self.indexTab)
#print(tabRect)
#print(tabRect.size())
pixmap = qg.QPixmap(tabRect.size())
tabBar.render(pixmap,qc.QPoint(),qg.QRegion(tabRect))
mimeData = qc.QMimeData()
drag = qg.QDrag(tabBar)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
cursor = qg.QCursor(qc.Qt.OpenHandCursor)
drag.setHotSpot(e.pos() - posInTab)
drag.setDragCursor(cursor.pixmap(),qc.Qt.MoveAction)
dropAction = drag.exec_(qc.Qt.MoveAction)
def mousePressEvent(self, e):
if e.button() == qc.Qt.RightButton:
self.tabBar.installEventFilter(self)
print('Right button pressed')
super(ColtTab, self).mousePressEvent(e)
def dragEnterEvent(self, e):
e.accept()
if e.source().parentWidget() != self:
return
# Helper function for retrieving the Tab index into a global Var
getTabIndex(self.indexOf(self.widget(self.indexTab)))
def dragLeaveEvent(self,e):
e.accept()
def dropEvent(self, e):
if e.source().parentWidget() == self:
return
e.setDropAction(qc.Qt.MoveAction)
e.accept()
counter = self.count()
if counter == 0:
self.addTab(e.source().parentWidget().widget(TABINDEX),e.source().tabText(TABINDEX))
else:
self.insertTab(counter + 1 ,e.source().parentWidget().widget(TABINDEX),e.source().tabText(TABINDEX))
print ('Tab dropped')
def mouseReleaseEvent(self, e):
if e.button() == qc.Qt.RightButton:
print('Right button released')
self.tabBar.removeEventFilter(self)
super(ColtTab, self).mouseReleaseEvent(e)
#---------------------------------------------------------------------------------#
Pic ->
Found this thread useful. Used your solution to create a self contained generic example in PyQt5. May help someone in the future.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class Tabs(QTabWidget):
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
self.setAcceptDrops(True)
self.tabBar = self.tabBar()
self.tabBar.setMouseTracking(True)
self.indexTab = None
self.setMovable(True)
self.addTab(QWidget(self), 'Tab One')
self.addTab(QWidget(self), 'Tab Two')
def mouseMoveEvent(self, e):
if e.buttons() != Qt.RightButton:
return
globalPos = self.mapToGlobal(e.pos())
tabBar = self.tabBar
posInTab = tabBar.mapFromGlobal(globalPos)
self.indexTab = tabBar.tabAt(e.pos())
tabRect = tabBar.tabRect(self.indexTab)
pixmap = QPixmap(tabRect.size())
tabBar.render(pixmap,QPoint(),QRegion(tabRect))
mimeData = QMimeData()
drag = QDrag(tabBar)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
cursor = QCursor(Qt.OpenHandCursor)
drag.setHotSpot(e.pos() - posInTab)
drag.setDragCursor(cursor.pixmap(),Qt.MoveAction)
dropAction = drag.exec_(Qt.MoveAction)
def dragEnterEvent(self, e):
e.accept()
if e.source().parentWidget() != self:
return
print(self.indexOf(self.widget(self.indexTab)))
self.parent.TABINDEX = self.indexOf(self.widget(self.indexTab))
def dragLeaveEvent(self,e):
e.accept()
def dropEvent(self, e):
print(self.parent.TABINDEX)
if e.source().parentWidget() == self:
return
e.setDropAction(Qt.MoveAction)
e.accept()
counter = self.count()
if counter == 0:
self.addTab(e.source().parentWidget().widget(self.parent.TABINDEX),e.source().tabText(self.parent.TABINDEX))
else:
self.insertTab(counter + 1 ,e.source().parentWidget().widget(self.parent.TABINDEX),e.source().tabText(self.parent.TABINDEX))
class Window(QWidget):
def __init__(self):
super().__init__()
self.TABINDEX = 0
tabWidgetOne = Tabs(self)
tabWidgetTwo = Tabs(self)
layout = QHBoxLayout()
self.moveWidget = None
layout.addWidget(tabWidgetOne)
layout.addWidget(tabWidgetTwo)
self.setLayout(layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
This is modified from someone elses code, perhaps one of the examples above.
Anyhow, it's a minimal code for tab-to-tab or tab-to-window drag / droping of tab's contents.
from PyQt5.QtWidgets import QTabWidget
from PyQt5.QtCore import Qt, QPoint, QMimeData
from PyQt5.QtGui import QPixmap, QRegion, QDrag, QCursor
class TabWidget(QTabWidget):
def __init__(self, parent=None, new=None):
super().__init__(parent)
self.setAcceptDrops(True)
self.tabBar().setMouseTracking(True)
self.setMovable(True)
if new:
TabWidget.setup(self)
def __setstate__(self, data):
self.__init__(new=False)
self.setParent(data['parent'])
for widget, tabname in data['tabs']:
self.addTab(widget, tabname)
TabWidget.setup(self)
def __getstate__(self):
data = {
'parent' : self.parent(),
'tabs' : [],
}
tab_list = data['tabs']
for k in range(self.count()):
tab_name = self.tabText(k)
widget = self.widget(k)
tab_list.append((widget, tab_name))
return data
def setup(self):
pass
def mouseMoveEvent(self, e):
if e.buttons() != Qt.RightButton:
return
globalPos = self.mapToGlobal(e.pos())
tabBar = self.tabBar()
posInTab = tabBar.mapFromGlobal(globalPos)
index = tabBar.tabAt(e.pos())
tabBar.dragged_content = self.widget(index)
tabBar.dragged_tabname = self.tabText(index)
tabRect = tabBar.tabRect(index)
pixmap = QPixmap(tabRect.size())
tabBar.render(pixmap,QPoint(),QRegion(tabRect))
mimeData = QMimeData()
drag = QDrag(tabBar)
drag.setMimeData(mimeData)
drag.setPixmap(pixmap)
cursor = QCursor(Qt.OpenHandCursor)
drag.setHotSpot(e.pos() - posInTab)
drag.setDragCursor(cursor.pixmap(),Qt.MoveAction)
drag.exec_(Qt.MoveAction)
def dragEnterEvent(self, e):
e.accept()
#self.parent().dragged_index = self.indexOf(self.widget(self.dragged_index))
def dragLeaveEvent(self,e):
e.accept()
def dropEvent(self, e):
if e.source().parentWidget() == self:
return
e.setDropAction(Qt.MoveAction)
e.accept()
tabBar = e.source()
self.addTab(tabBar.dragged_content, tabBar.dragged_tabname)
if __name__ == '__main__':
from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout
import sys
class Window(QWidget):
def __init__(self):
super().__init__()
self.dragged_index = None
tabWidgetOne = TabWidget(self)
tabWidgetTwo = TabWidget(self)
tabWidgetOne.addTab(QWidget(), "tab1")
tabWidgetTwo.addTab(QWidget(), "tab2")
layout = QHBoxLayout()
self.moveWidget = None
layout.addWidget(tabWidgetOne)
layout.addWidget(tabWidgetTwo)
self.setLayout(layout)
app = QApplication(sys.argv)
window = Window()
window1 = Window()
window.show()
window1.show()
sys.exit(app.exec_())

Categories