QRubberBand.geometry().intersects(???) How to find intersecting images in QGraphicsScene? - python

I found few codes which demonstrates intersects, but it was mainly buttons. Something like this:
for child in self.findChildren(QPushButton):
if rect.intersects(child.geometry( )):
selected.append(child)
But how do you find images intersecting in your GraphicsScene with "Marquee" selection(QRubberBand)? I tried replacing findChildren(QPushButton) with QPixmap, QGraphicsScene, QGraphicsPixmapItem it always giving me back empty list.

If you are using the QRubberBand that has the QGraphicsView you have to use the rubberBandChanged signal and next to the items method you get the items that are below the QRubberBand.
from PyQt5 import QtCore, QtGui, QtWidgets
import random
def create_pixmap():
pixmap = QtGui.QPixmap(100, 100)
pixmap.fill(QtGui.QColor(*random.sample(range(255), 3)))
return pixmap
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
self.rubberBandChanged.connect(self.on_rubberBandChanged)
for _ in range(5):
item = QtWidgets.QGraphicsPixmapItem(create_pixmap())
item.setPos(*random.sample(range(500), 2))
self.scene().addItem(item)
#QtCore.pyqtSlot("QRect", "QPointF", "QPointF")
def on_rubberBandChanged(
self, rubberBandRect, fromScenePoint, toScenePoint
):
r = QtCore.QRectF(fromScenePoint, toScenePoint)
selected = self.items(rubberBandRect)
print(selected)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = GraphicsView()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
If you are using another QRubberBand the logic is similar to that since you must use the items() method of QGraphicsView
from PyQt5 import QtCore, QtGui, QtWidgets
import random
def create_pixmap():
pixmap = QtGui.QPixmap(100, 100)
pixmap.fill(QtGui.QColor(*random.sample(range(255), 3)))
return pixmap
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self._rubberBand = QtWidgets.QRubberBand(
QtWidgets.QRubberBand.Rectangle, self.viewport()
)
self._rubberBand.hide()
self._origin = QtCore.QPoint()
for _ in range(5):
item = QtWidgets.QGraphicsPixmapItem(create_pixmap())
item.setPos(*random.sample(range(500), 2))
self.scene().addItem(item)
def mousePressEvent(self, event):
self._origin = event.pos()
self._rubberBand.setGeometry(QtCore.QRect(self._origin, QtCore.QSize()))
self._rubberBand.show()
super(GraphicsView, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
self._rubberBand.setGeometry(
QtCore.QRect(self._origin, event.pos()).normalized()
)
def mouseReleaseEvent(self, event):
self._rubberBand.setGeometry(
QtCore.QRect(self._origin, event.pos()).normalized()
)
selected = self.items(self._rubberBand.geometry())
print(selected)
self._rubberBand.hide()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = GraphicsView()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

Related

How to print the file path when the mouse hovers over an item in this QTreeView with QFileSystemModel

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()

Embed QGraphics in widget

I have a main widget inside a window who contains a lot of widgets. How can I insert a QGraphics view and a QGraphicsScene in that widget? I have not found a direct insertion method, so I am trying using a wrapper, in this case a box layout but it is not a good solution. The QGraphicsScene stands out from the layout limits.
Code:
class UiVentana(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setFixedSize(1500, 1015)
widget_central = QtWidgets.QWidget(self)
wrapper = QtWidgets.QHBoxLayout(widget_central)
scene = QtWidgets.QGraphicsScene(wrapper)
vista = QtWidgets.QGraphicsView(scene)
wrapper.addWidget(vista)
self.diedrico = Diedrico() # This is a class who draw things, not relevant
self.diedrico.setFixedSize(2000, 2000)
scene.addWidget(self.diedrico)
self.setCentralWidget(widget_central)
I would like to get this result:
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QWidget
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QPen, QColor
import sys
class Diedrico(QWidget):
def __init__(self, parent):
super().__init__(parent)
def paintEvent(self, event):
qp = QPainter(self)
qp.setPen(QPen(QColor(Qt.black), 5))
qp.drawRect(500, 500, 1000, 1000)
class UiVentana(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(UiVentana, self).__init__(parent)
self.resize(520, 520)
self.widget_central = QtWidgets.QWidget(self)
scrol = QtWidgets.QScrollArea(self.widget_central)
scrol.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scrol.setGeometry(QtCore.QRect(30, 30, 500, 500))
scrol.setWidgetResizable(False)
contenido = QtWidgets.QWidget()
contenido.setGeometry(QtCore.QRect(0, 0, 2000, 2000))
scrol.setWidget(contenido)
self.Diedrico = Diedrico(contenido)
self.Diedrico.setGeometry(QtCore.QRect(0, 0, 2000, 2000))
self.setCentralWidget(self.widget_central)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ui = UiVentana()
ui.show()
sys.exit(app.exec_())
But using QGraphics instead of a scroll area
The QGraphicsProxyWidget that is created using the widget takes into account the minimum size of the widget to set the boundingRect, and the QGraphicsScene uses the boundingRect to set the initial scene rect.
from PyQt5 import QtCore, QtGui, QtWidgets
class Diedrico(QtWidgets.QWidget):
def paintEvent(self, event):
qp = QtGui.QPainter(self)
pen = QtGui.QPen(QtGui.QColor(QtCore.Qt.black), 5)
qp.setPen(pen)
qp.drawRect(500, 500, 1000, 1000)
def minimumSizeHint(self):
return QtCore.QSize(2000, 2000)
class UiVentana(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(UiVentana, self).__init__(parent)
self.resize(520, 520)
widget_central = QtWidgets.QWidget()
self.setCentralWidget(widget_central)
lay = QtWidgets.QVBoxLayout(widget_central)
scene = QtWidgets.QGraphicsScene(self)
view = QtWidgets.QGraphicsView(scene)
diedrico = Diedrico()
scene.addWidget(diedrico)
lay.addWidget(view)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ui = UiVentana()
ui.show()
sys.exit(app.exec_())

Fit QGraphisItem in view

Is a method to fit any image in view (that I import in QPixmap) and keep aspect ratio>. I try many solution but non of those works.
Also I don't not sure what I need to fit?
QGraphicsScene in QGraphicsView? Or QPixmap in QGraphicsView?
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
scene = QtWidgets.QGraphicsScene()
self.setScene(scene)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = GraphicsView()
photo = QtGui.QPixmap("image.jpg")
w.scene().addPixmap(photo)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
You have to use the fitInView() method of QGraphicsView in the resizeEvent() method of QGraphicsView:
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
self.setScene(scene)
self.m_pixmap_item = self.scene().addPixmap(QtGui.QPixmap())
def setPixmap(self, pixmap):
self.m_pixmap_item.setPixmap(pixmap)
def resizeEvent(self, event):
if not self.m_pixmap_item.pixmap().isNull():
self.fitInView(self.m_pixmap_item, QtCore.Qt.KeepAspectRatio)
super(GraphicsView, self).resizeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = GraphicsView()
photo = QtGui.QPixmap("image.jpg")
w.setPixmap(photo)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

Move QLabel in absolute position with mouse move

I want to move the QLabel with the mouse movement (not like Drag&drop, 'object' disappears while moving). Clicked - moved - released. I did it to some extent, but I ran into a problem. QLabel shrinks as I move it or even disappears (like shrinks to 0 width). How to fix it or what more correct approach to do it?
(self.label_pos is needed to keep the mouse position relative inside self.label)
Or its just monitor's refresh rate issue? But in photoshop's gradient editor, that little color stop isn't shrikns. It's choppy because of refresh rate, but always the same size.
This is what I want to see, recorded using a screen capture program. The same thing I see in Photoshop
This is what I see, recorded on my phone. The quality is poor, but the difference is clearly visible anyway.
This Photoshop is also captured on my phone, here the “object” remains the same size, as in the example made using screen capture
Here is code from eyllanesc's answer, 'object' still shrinks :(
self.label = QLabel(self)
self.label.move(100, 100)
self.label.mousePressEvent = self.mouse_on
self.label.mouseReleaseEvent = self.mouse_off
def mouse_on(self, event):
self.bool = True
self.label_pos = event.pos()
def mouse_off(self, event):
self.bool = False
def mouseMoveEvent(self, event):
if self.bool:
self.label.move(event.x()-self.label_pos.x(), event.y()-self.label_pos.y())
Instead of using a QLabel I recommend using QGraphicsRectItem with a QGraphicsView since it is specialized in this type of tasks:
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
brush = QtWidgets.QApplication.palette().brush(QtGui.QPalette.Window)
self.setBackgroundBrush(brush)
rect_item = self.scene().addRect(
QtCore.QRectF(QtCore.QPointF(), QtCore.QSizeF(40, 80))
)
rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
rect_item.setBrush(QtGui.QBrush(QtGui.QColor("red")))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.setFixedSize(640, 480)
w.show()
sys.exit(app.exec_())
If you want to just scroll horizontally then overwrite the itemChange method of QGraphicsItem:
from PyQt5 import QtCore, QtGui, QtWidgets
class HorizontalItem(QtWidgets.QGraphicsRectItem):
def __init__(self, rect, parent=None):
super(HorizontalItem, self).__init__(rect, parent)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
def itemChange(self, change, value):
if (
change == QtWidgets.QGraphicsItem.ItemPositionChange
and self.scene()
):
return QtCore.QPointF(value.x(), self.pos().y())
return super(HorizontalItem, self).itemChange(change, value)
class Widget(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
brush = QtWidgets.QApplication.palette().brush(QtGui.QPalette.Window)
self.setBackgroundBrush(brush)
rect_item = HorizontalItem(
QtCore.QRectF(QtCore.QPointF(), QtCore.QSizeF(40, 80))
)
rect_item.setBrush(QtGui.QBrush(QtGui.QColor("red")))
self.scene().addItem(rect_item)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.setFixedSize(640, 480)
w.show()
sys.exit(app.exec_())
In the following code there is an example similar to what you want:
from PyQt5 import QtCore, QtGui, QtWidgets
class HorizontalItem(QtWidgets.QGraphicsRectItem):
def __init__(self, rect, parent=None):
super(HorizontalItem, self).__init__(rect, parent)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
def itemChange(self, change, value):
if (
change == QtWidgets.QGraphicsItem.ItemPositionChange
and self.scene()
):
return QtCore.QPointF(value.x(), self.pos().y())
return super(HorizontalItem, self).itemChange(change, value)
class Widget(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
brush = QtWidgets.QApplication.palette().brush(QtGui.QPalette.Window)
self.setBackgroundBrush(brush)
self.setFixedSize(640, 480)
size = self.mapToScene(self.viewport().rect()).boundingRect().size()
r = QtCore.QRectF(QtCore.QPointF(), size)
self.setSceneRect(r)
rect = QtCore.QRectF(
QtCore.QPointF(), QtCore.QSizeF(0.8 * r.width(), 80)
)
rect.moveCenter(r.center())
rect_item = self.scene().addRect(rect)
rect_item.setBrush(QtGui.QBrush(QtGui.QColor("salmon")))
item = HorizontalItem(
QtCore.QRectF(
rect.bottomLeft() + QtCore.QPointF(0, 20), QtCore.QSizeF(20, 40)
)
)
item.setBrush(QtGui.QBrush(QtGui.QColor("red")))
self.scene().addItem(item)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

How to execute a function on click of empty area of QTreeWidget

I am using Python3.6 and PySide2
I have a treewidget-A populated with employee list.
on click on employee (treewidget-A item) a another treeWidget-B will populate.
Now if I click on empty area (deselect item) but treeWidget-B still showing last clicked employee item details.
This is to populate treewidget items
self.treeWidget_A.itemClicked.connect(self.populate_employee)
self.treeWidget_A.currentItemChanged.connect(self.populate_employee)
How to execute clear_employee_data() function on click on empty area of QtreeWidget ?
def clear_employee_data(self):
self.treeWidget_B.clear()
You have to detect the click and verify that there is not an item:
from PySide2 import QtCore, QtWidgets
class TreeWidget(QtWidgets.QTreeWidget):
emptyClicked = QtCore.Signal()
def __init__(self, parent=None):
super(TreeWidget, self).__init__(parent)
for i in range(2):
it = QtWidgets.QTreeWidgetItem(self, ["item-{}".format(i)])
for j in range(3):
child_it = QtWidgets.QTreeWidgetItem(it, ["item-{}-{}".format(i, j)])
self.expandAll()
def mousePressEvent(self, event):
super(TreeWidget, self).mousePressEvent(event)
if not self.indexAt(event.pos()).isValid():
self.emptyClicked.emit()
#QtCore.Slot()
def on_empty_clicked():
print("clicked")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = TreeWidget()
w.emptyClicked.connect(on_empty_clicked)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
With Qt Designer use eventfilter:
from PySide2 import QtCore, QtGui, QtWidgets
from design import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
emptyClicked = QtCore.Signal()
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.treeWidget_A.viewport().installEventFilter(self)
self.emptyClicked.connect(self.on_emptyClicked)
def eventFilter(self, obj, event):
if obj is self.treeWidget_A.viewport() and event.type() == QtCore.QEvent.MouseButtonPress:
if not self.treeWidget_A.indexAt(event.pos()).isValid():
self.emptyClicked.emit()
return super(MainWindow, self).eventFilter(obj, event)
#QtCore.Slot()
def on_emptyClicked(self):
print("empty")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

Categories