I want to move my SimpleItem object if I move mouse pressing left button. I have successed getting the position of mouse cursor if I press the object. but I have no idea how to move the item to that position. Could you help me?
import sys
from PySide2 import QtGui, QtWidgets, QtCore
class SimpleItem(QtWidgets.QGraphicsItem):
def __init__(self):
QtWidgets.QGraphicsItem.__init__(self)
self.location = 1.0
def boundingRect(self):
penWidth = 1.0
return QtCore.QRectF(-10 - penWidth / 2, -10 - penWidth / 2,
20 + penWidth, 20 + penWidth)
def paint(self, painter, option, widget):
rect = self.boundingRect()
painter.drawRect(rect)
def mousePressEvent(self, event):
print("hello")
def mouseMoveEvent(self, event):
print(event.pos())
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
scene = QtWidgets.QGraphicsScene()
item = SimpleItem()
scene.addItem(item)
view = QtWidgets.QGraphicsView(scene)
view.show()
sys.exit(app.exec_())
In the case of the QGraphicsXXXItem it is not necessary to overwrite any method to enable the movement, it is enough to enable the flag QGraphicsItem::ItemIsMovable.
import sys
from PySide2 import QtGui, QtWidgets, QtCore
class SimpleItem(QtWidgets.QGraphicsItem):
def __init__(self):
QtWidgets.QGraphicsItem.__init__(self)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
def boundingRect(self):
penWidth = 1.0
return QtCore.QRectF(-10 - penWidth / 2, -10 - penWidth / 2,
20 + penWidth, 20 + penWidth)
def paint(self, painter, option, widget):
rect = self.boundingRect()
painter.drawRect(rect)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
scene = QtWidgets.QGraphicsScene()
item = SimpleItem()
scene.addItem(item)
view = QtWidgets.QGraphicsView(scene)
view.show()
sys.exit(app.exec_())
Related
The code below sketches a polygon on an image. I would like to draw a second identical shape inside the primary one with a gap of 0.3m. I have tried a couple of solutions but none of them worked in all use cases.
Please refer to the attached screenshot.
Context: The shape is drawn by combining a group of selected points using the QPolygon class.
import sys
from sympy import Polygon
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtCore import QLine, Qt, QPoint, QRect
from PyQt5.QtGui import QPixmap, QPainter,QColor,QPolygon
from PyQt5 import QtCore, QtGui, QtWidgets, uic
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.window_width, self.window_height =1200,800
self.setMinimumSize(self.window_width,self.window_height)
layout= QVBoxLayout()
self.setLayout(layout)
self.pix = QPixmap('image.jpg')
self.resize(self.pix.width(),self.pix.height())
# self.pix.fill(Qt.white)
# tableau
self.point = QPoint()
self.tab =[]
def paintEvent(self,event):
painter = QPainter(self)
pen = QtGui.QPen()
pen.setColor(QtGui.QColor('red'))
pen.setWidth(3)
painter.setPen(pen)
painter.drawPixmap(QPoint(),self.pix)
if not self.point.isNull():
# rect = QRect(self.begin,self.destination)
# painter.drawRect(rect.normalized())
line = QPoint(self.point)
painter.drawPoint(line)
def mousePressEvent(self,event):
if event.buttons() & Qt.LeftButton:
self.point = event.pos()
# self.destination = self.begin
self.update()
# def mouseMoveEvent(self,event):
# if event.buttons() & Qt.LeftButton:
# self.point = event.pos()
# self.update()
def mouseReleaseEvent(self,event):
pen = QtGui.QPen()
painter = QPainter(self.pix)
if event.button() == Qt.LeftButton:
# rect = QRect(self.begin,self.destination)
line = QPoint(self.point)
pen.setColor(QtGui.QColor('red'))
pen.setWidth(3)
painter.setPen(pen)
painter.drawPoint(line)
painter.setPen(QColor(168, 34, 3))
self.tab.append(self.point)
print(self.point.x,self.point.y)
self.point = QPoint()
# w = (rect.width()*12.5)/1056
# h = (rect.height()*12.5/1056)
# a=w*h
# print(w, h,a)
self.update()
if event.button() == Qt.RightButton:
points = QPolygon(self.tab)
pen.setColor(QtGui.QColor('red'))
painter.setPen(pen)
painter.drawPolygon(points)
#print(self.tab[0])
polytab=[]
for i in self.tab:
polytab.append((i.x(),i.y()))
print(Polygon(*polytab).area*(12.5/1056)*(12.5/1056))
print((self.tab[0].x()-self.tab[1].x())*(12.5/1056))
self.tab=[]
self.update()
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet('''QWidget{font-size:30px}''')
myAPP = MyApp()
myAPP.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
The algorithm is given a vertex so the associated edges must be translated in a parallel way and the intersection of these lines is a point of the desired polygon.
import sys
from PyQt5.QtCore import QLineF
from PyQt5.QtGui import QColor, QPainter, QPen, QPolygonF
from PyQt5.QtWidgets import QApplication, QWidget
def calculate_inner_polygon(polygon, offset):
if polygon.count() < 3:
return QPolygonF()
points = []
for i in range(polygon.count()):
pp = polygon[(i - 1 + polygon.count()) % polygon.count()]
pc = polygon[i]
pn = polygon[(i + 1 + polygon.count()) % polygon.count()]
line_0 = QLineF(pp, pc)
normal_0 = line_0.normalVector()
normal_0.setLength(offset)
line_0.translate(normal_0.dx(), normal_0.dy())
line_1 = QLineF(pc, pn)
normal_1 = line_1.normalVector()
normal_1.setLength(offset)
line_1.translate(normal_1.dx(), normal_1.dy())
t, point = line_0.intersects(line_1)
if t != QLineF.NoIntersection:
points.append(point)
return QPolygonF(points)
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.setMinimumSize(1200, 800)
self._points = list()
def paintEvent(self, event):
painter = QPainter(self)
painter.fillRect(self.rect(), QColor("white"))
if not self._points:
return
pen_width = 3
offset = -8
pen = QPen(QColor("red"))
outer_polygon = QPolygonF(self._points)
inner_polygon = calculate_inner_polygon(outer_polygon, offset)
for polygon in (outer_polygon, inner_polygon):
pen.setWidth(pen_width)
painter.setPen(pen)
painter.drawPolygon(polygon)
pen.setWidth(2 * pen_width)
painter.setPen(pen)
for point in polygon:
painter.drawPoint(point)
def mouseReleaseEvent(self, event):
self._points.append(event.pos())
self.update()
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyApp()
w.show()
sys.exit(app.exec_())
I'm trying to apply a zoom example that I've found on this site but it uses a QGraphicsScene and a QGraphicsView while I should use a simple QLabel. This is the code but it does not work. Can I zoom on a Qlabel or is it impossible?
The zoom should work with ctrl++ / ctrl+- shortcut.
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QLabel
class GuiZoom(QtWidgets.QMainWindow):
factor = 1.5
def __init__(self, parent=None):
super(GuiZoom, self).__init__(parent)
self.setFixedSize(800, 600)
self.lblZoom = QLabel("Facciamo lo zoom")
self.lblZoom.setStyleSheet("border:10px solid green")
self.setCentralWidget(self.lblZoom)
QtWidgets.QShortcut(
QtGui.QKeySequence(QtGui.QKeySequence.ZoomIn),
self.lblZoom,
context=QtCore.Qt.WidgetShortcut,
activated=self.zoom_in,
)
QtWidgets.QShortcut(
QtGui.QKeySequence(QtGui.QKeySequence.ZoomOut),
self.lblZoom,
context=QtCore.Qt.WidgetShortcut,
activated=self.zoom_out,
)
def zoom_in(self):
scale_tr = QtGui.QTransform()
scale_tr.scale(GuiZoom.factor, GuiZoom.factor)
tr = self.lblZoom.transform() * scale_tr
self.lblZoom.setTransform(tr)
def zoom_out(self):
scale_tr = QtGui.QTransform()
scale_tr.scale(GuiZoom.factor, GuiZoom.factor)
scale_inverted, invertible = scale_tr.inverted()
if invertible:
tr = self.lblZoom.transform() * scale_inverted
self.lblZoom.setTransform(tr)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ui = GuiZoom()
ui.show()
sys.exit(app.exec_())
Moreover can you explain me what the last lines of zoom_out functions do? Is this a way to invert the zoom?
One possible solution is to use a QGraphicsEffect to scale the QLabel's painting.
Note: The size of the widget is not changed but the painting is scaled.
from PyQt5 import QtCore, QtGui, QtWidgets
class ZoomEffect(QtWidgets.QGraphicsEffect):
factor = 1.5
_scale = 1.0
#property
def scale(self):
return self._scale
#scale.setter
def scale(self, scale):
self._scale = scale
self.update()
def draw(self, painter):
painter.setRenderHints(
QtGui.QPainter.Antialiasing
| QtGui.QPainter.SmoothPixmapTransform
| QtGui.QPainter.HighQualityAntialiasing
)
center = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates).center()
pixmap, offset = self.sourcePixmap(QtCore.Qt.DeviceCoordinates)
painter.setWorldTransform(QtGui.QTransform())
painter.translate(center)
painter.scale(self.scale, self.scale)
painter.translate(-center)
painter.drawPixmap(offset, pixmap)
def zoom_in(self):
self.scale *= self.factor
def zoom_out(self):
self.scale /= self.factor
class GuiZoom(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(GuiZoom, self).__init__(parent)
self.setFixedSize(800, 600)
self.lblZoom = QtWidgets.QLabel("Facciamo lo zoom")
self.lblZoom.setStyleSheet("border:10px solid green")
self.zoom_effect = ZoomEffect()
self.lblZoom.setGraphicsEffect(self.zoom_effect)
self.setCentralWidget(self.lblZoom)
QtWidgets.QShortcut(
QtGui.QKeySequence(QtGui.QKeySequence.ZoomIn),
self.lblZoom,
context=QtCore.Qt.WindowShortcut,
activated=self.zoom_effect.zoom_in,
)
QtWidgets.QShortcut(
QtGui.QKeySequence(QtGui.QKeySequence.ZoomOut),
self.lblZoom,
context=QtCore.Qt.WindowShortcut,
activated=self.zoom_effect.zoom_out,
)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ui = GuiZoom()
ui.show()
sys.exit(app.exec_())
I'm making a custom QTreeView with QFileSystem model, and I have a MouseMoveEvent set up to print the path of the item that is hovered over.
I'm way down the rabbit hole and doing all kinds of weird things to make this work.
Here is the latest minimal reproducible code:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(500, 300)
self.layout = QVBoxLayout()
self.setLayout(self.layout)
self.myList = CustomTreeWidget()
self.myList.model.setRootPath("/Volumes/Home/User/Desktop/testsrc")
self.myList.setObjectName("/Volumes/Home/User/Desktop/testsrc")
self.layout.addWidget(self.myList)
class CustomTreeWidget(QTreeView):
def __init__(self):
super().__init__()
self.model = QFileSystemModel()
self.model.setFilter(QDir.NoDotAndDotDot | QDir.Files)
self.setModel(self.model)
self.setAlternatingRowColors(True)
self.setDragDropMode(QAbstractItemView.DragDrop)
self.setIndentation(0)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.setMouseTracking(True)
self.model.directoryLoaded.connect(self._runwhenloaded)
def _runwhenloaded(self):
self.setRootIndex(self.model.index(self.objectName()))
self.model.setRootPath(self.objectName())
def mouseMoveEvent(self, event):
prev = ""
if self.selectedIndexes():
prev = self.selectedIndexes()[0]
x = event.x()
y = event.y()
self.setSelection(QRect(x, y, 1, 1), QItemSelectionModel.ClearAndSelect)
self.setCurrentIndex(self.selectedIndexes()[0])
print(self.model.filePath(self.currentIndex()))
if prev:
self.setCurrentIndex(prev)
# pos = QCursor.pos()
# indexat = self.indexAt(pos).row() # why -1?
# print(indexat) # why -1?
# print(self.indexAt(pos).row())
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Obviously this example is not proper at all, as it destroys multiple selections and scrolls to the previously selected item whenever the mouse moves, and just a hack in general.
I've gone through many iterations and read everything I could put my hands on but I can't figure it out.
The closest answer seems to be HERE, but it's in C and I don't understand it.
So the question is: How to print the file path when the mouse hovers over an item in this QTreeView?
A possible solution is to create an event filter that tracks the hover events and, according to that information, emits a signal that has the QModelIndex:
import sys
from PyQt5.QtCore import (
pyqtSignal,
pyqtSlot,
Qt,
QDir,
QEvent,
QModelIndex,
QObject,
QPersistentModelIndex,
QStandardPaths,
)
from PyQt5.QtWidgets import (
QAbstractItemView,
QApplication,
QFileSystemModel,
QMainWindow,
QTreeView,
)
from PyQt5 import sip
class HoverViewHelper(QObject):
hovered = pyqtSignal(QModelIndex)
def __init__(self, view):
super().__init__(view)
self._current_index = QPersistentModelIndex()
if not isinstance(view, QAbstractItemView):
raise TypeError(f"The {view} must be of type QAbstractItemView")
self._view = view
self.view.viewport().setAttribute(Qt.WA_Hover)
self.view.viewport().installEventFilter(self)
#property
def view(self):
return self._view
def eventFilter(self, obj, event):
if sip.isdeleted(self.view):
return True
if self.view.viewport() is obj:
if event.type() in (QEvent.HoverMove, QEvent.HoverEnter):
p = event.pos()
index = self.view.indexAt(p)
self._update_index(index)
elif event.type() == QEvent.HoverLeave:
if self._current_index.isValid():
self._update_index(QModelIndex())
return super().eventFilter(obj, event)
def _update_index(self, index):
pindex = QPersistentModelIndex(index)
if pindex != self._current_index:
self._current_index = pindex
self.hovered.emit(QModelIndex(self._current_index))
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.model = QFileSystemModel()
self.model.setRootPath(QDir.rootPath())
self.view = QTreeView()
self.view.setModel(self.model)
path = QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)
self.view.setRootIndex(self.model.index(path))
self.setCentralWidget(self.view)
helper = HoverViewHelper(self.view)
helper.hovered.connect(self.handle_hovered)
#pyqtSlot(QModelIndex)
def handle_hovered(self, index):
if not index.isValid():
return
path = self.model.filePath(index)
print(f"path: {path}")
def main():
app = QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
app.exec_()
if __name__ == "__main__":
main()
In below figure I have QGraphicsPathItem on scene as red portion and override it's shape as blue portion. I want when the red space is dragged and moved then the item is lengthened or shortened linearly, and when the blue space is dragged then the entire item must be moved.
Here is what I tried...
import sys
from PyQt5.QtCore import QRectF, Qt, QPointF
from PyQt5.QtGui import QPainterPath, QPen, QPainterPathStroker, QPainter
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsScene, QGraphicsView, QGraphicsPathItem, QGraphicsItem
class Item(QGraphicsPathItem):
circle = QPainterPath()
circle.addEllipse(QRectF(-5, -5, 10, 10))
def __init__(self):
super(Item, self).__init__()
self.setPath(Item.circle)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
def paint(self, painter, option, widget):
color = Qt.red if self.isSelected() else Qt.black
painter.setPen(QPen(color, 2, Qt.SolidLine))
painter.drawPath(self.path())
# To paint path of shape
painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine))
painter.drawPath(self.shape())
def shape(self):
startPoint = self.mapFromScene(self.pos())
endPoint = self.mapFromScene(QPointF(10, 10))
path = QPainterPath(startPoint)
path.lineTo(endPoint)
stroke = QPainterPathStroker()
stroke.setWidth(10)
return stroke.createStroke(path)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QMainWindow()
window.show()
scene = QGraphicsScene()
scene.setSceneRect(0, 0, 200, 200)
view = QGraphicsView()
view.setScene(scene)
window.setCentralWidget(view)
scene.addItem(Item())
sys.exit(app.exec_())
I am getting output as disturbed path
Handling the task of resizing and stretching in the same item is complicated, so to avoid it I have used 2 items: A handle and a Pipe. Thus each one manages his own task and updates the position of the other elements:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class HandleItem(QtWidgets.QGraphicsPathItem):
def __init__(self, parent=None):
super().__init__(parent)
path = QtGui.QPainterPath()
path.addEllipse(QtCore.QRectF(-5, -5, 10, 10))
self.setPath(path)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
self._pipe_item = None
#property
def pipe_item(self):
return self._pipe_item
#pipe_item.setter
def pipe_item(self, item):
self._pipe_item = item
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
ip = self.pipe_item.mapFromScene(value)
self.pipe_item.end_pos = ip
elif change == QtWidgets.QGraphicsItem.ItemSelectedChange:
color = QtCore.Qt.red if value else QtCore.Qt.black
self.setPen(QtGui.QPen(color, 2, QtCore.Qt.SolidLine))
return super().itemChange(change, value)
class PipeItem(QtWidgets.QGraphicsPathItem):
def __init__(self, parent=None):
super().__init__(parent)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
self._end_pos = QtCore.QPointF()
self._handle = HandleItem()
self.handle.pipe_item = self
self.end_pos = QtCore.QPointF(10, 10)
self.handle.setPos(self.end_pos)
self.setPen(QtGui.QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine))
#property
def handle(self):
return self._handle
#property
def end_pos(self):
return self._end_pos
#end_pos.setter
def end_pos(self, p):
path = QtGui.QPainterPath()
path.lineTo(p)
stroke = QtGui.QPainterPathStroker()
stroke.setWidth(10)
self.setPath(stroke.createStroke(path))
self._end_pos = p
def paint(self, painter, option, widget):
option.state &= ~QtWidgets.QStyle.State_Selected
super().paint(painter, option, widget)
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemSceneHasChanged:
if self.scene():
self.scene().addItem(self.handle)
elif change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
p = self.mapToScene(self.end_pos)
self.handle.setPos(p)
return super().itemChange(change, value)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
scene = QtWidgets.QGraphicsScene(sceneRect=QtCore.QRectF(0, 0, 200, 200))
item = PipeItem()
scene.addItem(item)
view = QtWidgets.QGraphicsView(scene)
window = QtWidgets.QMainWindow()
window.setCentralWidget(view)
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
UPDATE:
If you want the logic you want to be implemented then it is more complicated. The cause of the error is that the paint() method uses the boundingRect() to set the paint area, but in your case it does not take into account that it varies, a possible solution is the following:
class Item(QGraphicsPathItem):
circle = QPainterPath()
circle.addEllipse(QRectF(-5, -5, 10, 10))
# ...
def boundingRect(self):
return self.shape().boundingRect()
I want to display an image in a QGraphicsView, actually in QGraphicsScene, this is the easy part, bu, when I move the cursor over the image, I want to see the X and Y coordinates lines (the yellow lines), like in this image, can anyone explain me how to do this?
To implement what you want there are 2 tasks:
Obtain the position of the cursor, for this case the flag mouseTracking is enabled so that mouseMoveEvent() is called where the position is obtained.
Paint on the top layer, for this we use the drawForeground() function.
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphicsScene(QtWidgets.QGraphicsScene):
def drawForeground(self, painter, rect):
super(GraphicsScene, self).drawForeground(painter, rect)
if not hasattr(self, "cursor_position"):
return
painter.save()
pen = QtGui.QPen(QtGui.QColor("yellow"))
pen.setWidth(4)
painter.setPen(pen)
linex = QtCore.QLineF(
rect.left(),
self.cursor_position.y(),
rect.right(),
self.cursor_position.y(),
)
liney = QtCore.QLineF(
self.cursor_position.x(),
rect.top(),
self.cursor_position.x(),
rect.bottom(),
)
for line in (linex, liney):
painter.drawLine(line)
painter.restore()
def mouseMoveEvent(self, event):
self.cursor_position = event.scenePos()
self.update()
super(GraphicsScene, self).mouseMoveEvent(event)
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setMouseTracking(True)
scene = GraphicsScene(QtCore.QRectF(-200, -200, 400, 400), self)
self.setScene(scene)
if __name__ == "__main__":
import sys
import random
app = QtWidgets.QApplication(sys.argv)
w = GraphicsView()
for _ in range(4):
r = QtCore.QRectF(
*random.sample(range(-200, 200), 2),
*random.sample(range(50, 150), 2)
)
it = w.scene().addRect(r)
it.setBrush(QtGui.QColor(*random.sample(range(255), 3)))
w.resize(640, 480)
w.show()
sys.exit(app.exec_())