PyQt5: How to flip QGraphicsItem parent item only - pyqt5

I am trying to perform flipping on a QGraphicsItem that has child and grandchild QGraphicsItem.
The original item looks like this:
(The blue rectangle and text are child and the number inside it is grandchild
I apply the following transformation to the parent item:
parentItem.setTransformOriginPoint(parentItem.boundingRect().center())
parentItem.setTransform(QTransform.fromScale(-1, 1))
Result after flipping parent item:
Since I want to reflip the text and number to be readable, I attempt to re-flip them after the parent's transformation as followed:
# For the child text
child.setTransformOriginPoint(child.boundingRect().center())
child.setTransform(QTransform.fromScale(-1, 1), True)
...
# For the grandchild number
grandchild.setTransformOriginPoint(grandchild.boundingRect().center())
grandchild.setTransform(QTransform.fromScale(-1, 1), True)
Here is the result after re-flipped the child and grandchild item:
.
It seems that the translation is not correct. Can someone advice?
Thanks!
Minimal reproducible example below:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class ParentItem(QGraphicsRectItem):
def __init__(self, pos, name, parent=None):
w, h = 550, 220
super().__init__(-w/2, -h/2, w, h)
self.setPos(pos)
self.name = ChildText(self.boundingRect().topLeft() - QPointF(0, 100), f">NAME{name}", self)
self.value = ChildText(self.boundingRect().topLeft()- QPointF(0, 50), f">VALUE_{name}", self)
self.ChildPad1 = ChildPad(QPointF(-150, 0), "1", self)
self.ChildPad2 = ChildPad(QPointF(+150, 0), "2", self)
self.color = QColor(192, 192, 192)
self.setPen(QPen(self.color, 5))
self.setFlag(self.ItemIsMovable, True)
def flipParent(self):
self.setTransformOriginPoint(self.boundingRect().center())
self.setTransform(QTransform.fromScale(-1, 1))
def reflipChilds(self):
# Child Texts
self.name.setTransformOriginPoint(self.name.boundingRect().center())
self.name.setTransform(QTransform.fromScale(-1, 1))
self.value.setTransformOriginPoint(self.value.boundingRect().center())
self.value.setTransform(QTransform.fromScale(-1, 1))
# GrandChild Numbers
for child in self.childItems():
if isinstance(child, ChildPad):
child.Number.setTransformOriginPoint(child.Number.boundingRect().center())
child.Number.setTransform(QTransform.fromScale(-1, 1))
class ChildText(QGraphicsTextItem):
def __init__(self, pos, text=">Text", parent=None):
super().__init__(parent)
self.setPos(pos)
self.parent = parent
self.text = text
self.color = QColor(255, 0, 0)
self.setDefaultTextColor(self.color)
self.setFlag(self.ItemIsMovable, True)
f = QFont()
f.setPointSizeF(min(self.parent.boundingRect().width()/8, self.parent.boundingRect().height()/8))
self.setFont(f)
self.setHtml(f"<p><center>{self.text}</center></p>")
class ChildPad(QGraphicsRectItem):
def __init__(self, pos, pinNumber, parent=None):
w, h = 200, 100
super().__init__(-w/2, -h/2, w, h, parent)
self.setPos(pos)
self.parent = parent
self.color = QColor(255, 0, 0)
self.setPen(QPen(self.color, Qt.MiterJoin, 1))
self.setBrush(QBrush(self.color))
self.Number = GrandChildNumber(pinNumber, self)
class GrandChildNumber(QGraphicsTextItem):
def __init__(self, pinNumber, parent=None):
super().__init__(parent)
self.parent = parent
self.color = QColor(32, 32, 32)
self.setHtml(f"{pinNumber}")
self.moveToParentCenter()
def moveToParentCenter(self):
f = QFont()
f.setPointSizeF(min(self.parent.boundingRect().width()/4, self.parent.boundingRect().height()/4))
self.setFont(f)
rect = self.boundingRect()
rect.moveCenter(self.parent.boundingRect().center())
self.setPos(rect.topLeft())
self.adjustSize()
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
scene = QGraphicsScene()
# No Transformation applied
originalItem = ParentItem(QPointF(300, 100), "ORIGINAL", scene)
scene.addItem(originalItem)
# Flipped the whole parent item
flipParentItem = ParentItem(QPointF(300, 500), "FLIPP_PARENT", scene)
flipParentItem.flipParent()
scene.addItem(flipParentItem)
# Flipped the whole parent item, then reflip the Text and Number
reflipChildItem = ParentItem(QPointF(300, 900), "REFLIP_CHILDS", scene)
flipParentItem.flipParent()
reflipChildItem.reflipChilds()
scene.addItem(reflipChildItem)
view = QtWidgets.QGraphicsView(scene)
view.setRenderHints(QtGui.QPainter.Antialiasing)
view.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

If you want to ignore transformations, you should use the ItemIgnoresTransformations flag.
Then, be aware that setTransformation() doesn't apply the transformation "on top" of the existing one, but completely sets a new transformation on the item (transforms inherited from the parent are not considered).
A proper flip() function should toggle the transformation, so it must consider the current transform() and flip it.
Now, the problem with text items is that they always use the origin point as the top left of their contents. While the basic repositioning might work for generic usage, it will not whenever any parent has a transformation.
The problem you're seeing is because you are just mapping the position based on the parent, but since the parent is "flipped", the top left corner of the resulting rect is on the opposite side (relative to the center) in parent coordinates.
To properly get the actual position relative to the parent, you must always use the cumulative transformations, which means map coordinates to the scene and map them back to the parent.
In order to make the code simpler to understand, I moved the repositioning function to the parent of the text item.
class ParentItem(QGraphicsRectItem):
# ...
def flip(self):
self.setTransformOriginPoint(self.boundingRect().center())
self.setTransform(self.transform().scale(-1, 1))
for child in self.childItems():
if isinstance(child, ChildPad):
child.updateNumber()
class ChildPad(QGraphicsRectItem):
def __init__(self, pos, pinNumber, parent=None):
w, h = 200, 100
super().__init__(-w/2, -h/2, w, h, parent)
self.setPos(pos)
self.parent = parent
self.color = QColor(255, 0, 0)
self.setPen(QPen(self.color, Qt.MiterJoin, 1))
self.setBrush(QBrush(self.color))
self.number = GrandChildNumber(pinNumber, self)
self.updateNumber()
def updateNumber(self):
br = self.boundingRect()
f = QFont()
f.setPointSizeF(min(br.width() / 4, br.height() / 4))
self.number.setFont(f)
# get the "visual" rect of the parent in scene coordinates
parentRect = self.mapToScene(br).boundingRect()
rect = self.number.boundingRect()
rect.moveCenter(parentRect.center())
# map the new rect position *from* the scene in local coordinates
topLeft = self.mapFromScene(rect.topLeft())
self.number.setPos(topLeft)
class GrandChildNumber(QGraphicsTextItem):
def __init__(self, pinNumber, parent=None):
super().__init__(parent)
self.parent = parent
self.setFlag(self.ItemIgnoresTransformations)
self.color = QColor(32, 32, 32)
self.setHtml(str(pinNumber))

Related

How to open context menu for an object that is empty, but has a size?

I am creating a GUI for a dependency graphing software... And am not able to figure out how to get a context menu to open for my lines.
What I want to do, right click on/near a MyLine widget and open a context menu... What is happening right clicks are not detected.
It is currently not detecting right clicks on the line widgets location to open a context menu (Purpose of this is to allow the user to delete/edit lines by right clicking on them).
What am I doing wrong here?
class MyLine(QWidget):
def __init__(self, destination: Node, source: Node, parent=None):
super().__init__(parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showMenu)
self.destination = destination
self.source = source
self.setAutoFillBackground(True)
p = self.palette()
p.setColor(self.backgroundRole(), Qt.red)
self.setPalette(p)
def update_line_size(self):
origin = self.source.get_line_draw_pos(self.destination.pos())
destination = self.destination.get_line_draw_pos(self.source.pos())
leftcornerX = origin.x() if origin.x() < destination.x() else destination.x()
leftcornerY = origin.y() if origin.y() < destination.y() else destination.y()
sizeX = abs(origin.x() - destination.x())
sizeY = abs(origin.y() - destination.y())
self.setGeometry(leftcornerX, leftcornerY, sizeX, sizeY)
def showMenu(self, _):
menu = QMenu()
menu.addAction("Delete", self.remove)
menu.exec_(self.cursor().pos())
def draw(self, painter: QPainter):
origin = self.source.get_line_draw_pos(self.destination.pos())
destination = self.destination.get_line_draw_pos(self.source.pos())
painter.drawLine(origin, destination)
# DRAW ARROW HEAD
ARROW_SIZE = 10 # Might change
line_angle = calculate_line_angle(destination, origin)
draw_arrow_head(destination, painter, line_angle, ARROW_SIZE)
def remove(self):
self.parent().delete_line(self)
self.deleteLater()
Edit:
required types for reproducibility
class Node(QLabel):
def __init__(self, text: str, parent=None):
super().__init__(text, parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showMenu)
def get_line_draw_pos(self, other_side: QPoint):
x = self.pos().x() if other_side.x() < self.pos().x() else (self.pos().x() + self.width())
y = self.pos().y() if other_side.y() < self.pos().y() else (self.pos().y() + self.height())
return QPoint(x, y)
def showMenu(self, _):
pass #purposefully left as a stub
def calculate_line_angle(destination: QPoint, origin: QPoint):
return math.atan2(destination.y() - origin.y(), destination.x() - origin.x())
def draw_arrow_head(destination: QPoint, painter: QPainter, line_angle: float, arrow_size: float = 10):
angle1 = math.radians(22.5) + line_angle
angle2 = math.radians(-22.5) + line_angle
arrow1 = QPoint( int(destination.x() - arrow_size * math.cos(angle1)), int(destination.y() - arrow_size * math.sin(angle1)))
arrow2 = QPoint( int(destination.x() - arrow_size * math.cos(angle2)), int(destination.y() - arrow_size * math.sin(angle2)))
painter.drawLine(destination, arrow1)
painter.drawLine(destination, arrow2)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setAcceptDrops(True) # add a drop rule
self.setMouseTracking(True)
self.track_origin = None
self.track_mouse = QPoint(0,0)
self.lines = []
def paintEvent(self, event):
painter = QPainter(self)
for line in self.lines:
line.draw(painter)
line.update_line_size()
def connectNodes(self, destination: Node, source: Node):
self.lines.append(MyLine(destination, source))
self.update()
def delete_line(self, line: MyLine):
self.lines.remove(line)
self.update()
app = QApplication([])
window = MainWindow()
window.setWindowTitle("Right Click to remove label")
window.setGeometry(100, 100, 400, 200)
window.move(60,15)
nodes = []
for index, node_name in enumerate(["hello.txt", "not_a_villain.txt", "nope.txt"]):
node = Node(node_name, window)
node.move(50 + index*100, 50 + (index%2) * 50)
nodes.append(node)
window.connectNodes(nodes[0], nodes[1])
window.connectNodes(nodes[0], nodes[2])
window.connectNodes(nodes[1], nodes[2])
window.show()
sys.exit(app.exec_())

PyQt5 - How to calculate corner points of a QGraphicsRectItem after rotation by its center point?

My problem is that I couldn't find the pixel values of each corner points of a HighwayItem (which is a QGraphicsRectItem) after rotation it by angle theta about the center point of it.
I used the Rotation Matrix which explained here and I also looked thisexplanation. But, I cannot find the true values.
Any help will be great. Thanks.
Here is MapViewer() class. A HighwayItem is created in this view.
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import Qt, QPoint, QPointF, QRectF
from PyQt5.QtWidgets import QGraphicsScene, \
QGraphicsView, QGraphicsPixmapItem, \
from class_graphical_items import HighwayItem
class MapViewer(QGraphicsView):
def __init__(self, parent, ui):
super(MapViewer, self).__init__(parent)
self.ui = ui
# Attributes for highway
self.add_highway_control = False
self.current_highway = None
self.start = QPointF()
self.hw_counter = 0
self._scene = QGraphicsScene(self)
self._map = QGraphicsPixmapItem()
self._scene.addItem(self._map)
self.setScene(self._scene)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setFrameShape(QtWidgets.QFrame.NoFrame)
def mousePressEvent(self, event):
if self._map.isUnderMouse():
if self.add_highway_control:
# Create a yellow highway
self.current_highway = HighwayItem(self._scene, self.ui)
self.hw_counter += 1
self.start = self.mapToScene(event.pos()).toPoint()
r = QRectF(self.start, self.start)
self.current_highway.setRect(r)
self._scene.addItem(self.current_highway)
# When adding HW, set drag mode NoDrag
self.setDragMode(QGraphicsView.NoDrag)
super(MapViewer, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.add_highway_control and self.current_highway is not None:
# When adding HW, set drag mode NoDrag
self.setDragMode(QGraphicsView.NoDrag)
r = QRectF(self.start, self.mapToScene(event.pos()).toPoint()).normalized()
self.current_highway.setRect(r)
super(MapViewer, self).mouseReleaseEvent(event)
def mouseReleaseEvent(self, event):
if self.add_highway_control:
if self.current_highway is not None:
# When finish the adding HW, set drag mode ScrollHandDrag
self.setDragMode(QGraphicsView.ScrollHandDrag)
self.update_item_dict(self.current_highway)
self.update_item_table(self.current_highway)
self.current_highway = None
self.add_highway_control = False
super(MapViewer, self).mouseReleaseEvent(event)
This is the HighwayItem class. It has some specs like color, opacity etc.
By doubleclicking on created HighwayItem, I'm activating a spinbox which was in a QTreeWidget in main window (ui).
By changing the spinbox value, the user can rotate the item.
class HighwayItem(QGraphicsRectItem):
def __init__(self, scene, ui):
QGraphicsRectItem.__init__(self)
self.scene = scene
self.ui = ui
self.setBrush(QtCore.Qt.yellow)
self.setOpacity(0.5)
self.setZValue(4.0)
self.setFlag(QGraphicsItem.ItemIsMovable, True)
self.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
self.setFlag(QGraphicsItem.ItemIsFocusable, True)
self.setAcceptHoverEvents(True)
# Here, I'm activating the spinbox by double clicking
# on HighwayItem. In spinbox, I'm entering the rotation angle
# of HighwayItem.
def mouseDoubleClickEvent(self, event):
selected_item = self.scene.selectedItems()
if selected_item:
for i in range(self.ui.treeWidget_objects.topLevelItemCount()):
toplevel_item = self.ui.treeWidget_objects.topLevelItem(i)
heading_item = toplevel_item.child(2)
spinbox = self.ui.treeWidget_objects.itemWidget(heading_item, 2)
if str(toplevel_item.text(2)) == str(selected_item[0]):
if 'HighwayItem' in str(selected_item[0]):
spinbox.setEnabled(True)
else:
spinbox.setEnabled(False)
This is the HWHeadingSpinBox() class which sets the rotation angle of HWItem. My problem starts here. In rotate_hw() method, I am transforming the created HighwayItem by its center point and giving it a rotation by its center point.
BUT, when I try to calculate new corners of hw in calc_rotated_coords() method, I'm messing up.
class HWHeadingSpinBox(QSpinBox):
def __init__(self, viewer, selected_hw):
QSpinBox.__init__(self)
self.selected_hw = selected_hw
self.viewer = viewer
# First coords of HW
tl = self.selected_hw.rect().topLeft()
tr = self.selected_hw.rect().topRight()
br = self.selected_hw.rect().bottomRight()
bl = self.selected_hw.rect().bottomLeft()
self.temp_list = [tl, tr, br, bl]
self.setRange(-180, 180)
self.setSuffix('°')
self.setEnabled(False)
self.valueChanged.connect(self.rotate_hw)
def heading_val(self):
return self.value()
def rotate_hw(self):
angle = self.heading_val()
self.selected_hw.prepareGeometryChange()
offset = self.selected_hw.boundingRect().center()
self.selected_hw.sceneBoundingRect().center()
transform = QTransform()
transform.translate(offset.x(), offset.y())
transform.rotate(-angle)
transform.translate(-offset.x(), -offset.y())
self.selected_hw.setTransform(transform)
# br_rect = self.selected_hw.sceneBoundingRect()
# sbr_rect = self.selected_hw.sceneBoundingRect()
# r_rect = self.selected_hw.sceneBoundingRect()
#
# rectitem = QtWidgets.QGraphicsRectItem(br_rect)
# rectitem.setBrush(Qt.red)
# self.viewer._scene.addItem(rectitem)
#
# rectitem = QtWidgets.QGraphicsRectItem(sbr_rect)
# rectitem.setBrush(Qt.green)
# self.viewer._scene.addItem(rectitem)
#
# rectitem = QtWidgets.QGraphicsRectItem(r_rect)
# rectitem.setBrush(Qt.blue)
# self.viewer._scene.addItem(rectitem)
def calc_rotated_coords(self):
# center point
cx = self.selected_hw.rect().center().x()
cy = self.selected_hw.rect().center().y()
# rotation angle
theta = math.radians(angle)
rotated_corners = []
for item in self.temp_list:
x = item.x()
y = item.y()
temp_x = x - cx
temp_y = y - cy
rot_x = temp_x * math.cos(theta) + temp_y * math.sin(theta)
rot_y = -temp_x * math.sin(theta) + temp_y * math.cos(theta)
rotated_corners.append([rot_x, rot_y])
self.temp_list = rotated_corners
print("\nPIXEL VALUES OF HW: \n{}".format(self.temp_list))
Here is the solution:
I added the itemChange(self, change, value) event in to HighwayItem and if change is ItemPositionHasChanged, I calculated all items' corners as such:
def itemChange(self, change, value):
if change == QGraphicsItem.ItemPositionHasChanged:
top_left = self.mapToScene(self.rect().topLeft())
top_right = self.mapToScene(self.rect().topRight())
bottom_left = self.mapToScene(self.rect().bottomLeft())
bottom_right = self.mapToScene(self.rect().bottomRight())
changed_pos = [top_left, top_right, bottom_right, bottom_left]
return super(HighwayItem, self).itemChange(change, value)

Pyqt5 synchronization between two tabwidget which are in different window

I want to make new window by double click tabwidget.
and copy tabwidget's (child which is tablewidget) to new window.
and finally, changing item of new window's tablewidget needs to change mainwindow's tablewidget.
would it be possible?
I have seen this, that answer does copy tabwidget to new window
but remove mainwindow tabwidget.
here is I worked so far.
I managed to make new dialog by double click, but other things.. I dont' have any clues. can anyone can help?
#!/usr/bin/python
# -*- coding: utf8 -*-
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class DetachableTabWidget(QTabWidget):
def __init__(self, parent=None):
QTabWidget.__init__(self)
self.tabBar = self.TabBar(self)
self.tabBar.onDetachTabSignal.connect(self.detachTab)
self.setTabBar(self.tabBar)
print("DetachableTabWidget")
#pyqtSlot(int, QPoint)
def detachTab(self, index, point):
print("detachTab")
all_list = []
list1 = []
list2 = []
name = self.tabText(index)
icon = self.tabIcon(index)
if icon.isNull():
icon = self.window().windowIcon()
contentWidget = self.widget(index)
contentWidgetRect = contentWidget.frameGeometry()
tablewidgetA = contentWidget.findChild(QTableWidget)
for i in range(tablewidgetA.rowCount()):
list1.append(tablewidgetA.item(i, 0).text())
list2.append(tablewidgetA.item(i, 1).text())
all_list.append(list1)
all_list.append(list2)
detachedTab = self.DetachedTab(all_list)
detachedTab.setWindowModality(Qt.NonModal)
detachedTab.setWindowTitle(name)
detachedTab.setWindowIcon(icon)
detachedTab.setObjectName(name)
detachedTab.setGeometry(contentWidgetRect)
detachedTab.move(point)
detachedTab.exec_()
class DetachedTab(QDialog) :
onCloseSignal = pyqtSignal(QWidget,type(''), QIcon)
# def __init__(self, contentWidget, parent=None):
def __init__(self, all_list, parent=None) :
print("DetachedTab")
super().__init__()
layout = QVBoxLayout(self)
table = QTableWidget()
table.setColumnCount(len(all_list))
table.setRowCount(len(all_list[0]))
for col in range(len(all_list)) :
for row in range(len(all_list[col])) :
item = QTableWidgetItem(all_list[col][row])
table.setItem(row, col, item)
layout.addWidget(table)
table.show()
class TabBar(QTabBar):
onDetachTabSignal = pyqtSignal(int, QPoint)
onMoveTabSignal = pyqtSignal(int, int)
def __init__(self, parent=None):
QTabBar.__init__(self, parent)
self.setAcceptDrops(True)
self.setElideMode(Qt.ElideRight)
self.setSelectionBehaviorOnRemove(QTabBar.SelectLeftTab)
self.dragStartPos = QPoint()
self.dragDropedPos = QPoint()
self.mouseCursor = QCursor()
self.dragInitiated = False
def mouseDoubleClickEvent(self, event) :
event.accept()
self.onDetachTabSignal.emit(self.tabAt(event.pos()), self.mouseCursor.pos())
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.centralTabs = DetachableTabWidget()
self.setCentralWidget(self.centralTabs)
self.setFixedWidth(600)
self.setFixedHeight(600)
#tab 1
self.tab_1 = QWidget()
self.centralTabs.addTab(self.tab_1,"Tab 1")
vbox = QVBoxLayout()
Table = QTableWidget(2, 2)
vbox.addWidget(Table)
item = QTableWidgetItem("table 1 content")
Table.setItem( 0, 0, item)
item = QTableWidgetItem("table 2 content")
Table.setItem( 0, 1, item)
item = QTableWidgetItem("table 3 content")
Table.setItem( 1, 0, item)
item = QTableWidgetItem("table 4 content")
Table.setItem( 1, 1, item)
vbox.setAlignment(Qt.AlignTop)
self.tab_1.setLayout(vbox)
#tab 2
self.tab_2 = QWidget()
self.centralTabs.addTab(self.tab_2,"Tab 2")
vbox = QVBoxLayout()
Table = QTableWidget(2, 2)
item = QTableWidgetItem("table 2 content")
Table.setItem( 0, 0, item)
item = QTableWidgetItem("table 3 content")
Table.setItem( 0, 1, item)
item = QTableWidgetItem("table 4 content")
Table.setItem( 1, 0, item)
item = QTableWidgetItem("table 5 content")
Table.setItem( 1, 1, item)
vbox.addWidget(Table)
vbox.setAlignment(Qt.AlignTop)
self.tab_2.setLayout(vbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
ex.show()
sys.exit(app.exec_( ))
If you want to synchronize data between item views, you have to use a common model. Since you're using a QTableWidget (which has an internal, private model, and a higher level item view) you can create a new window using a QTableView instead, and set its model to the source. In that case, you don't need to "copy" row/column/data, you only need to use the source model.
Here's a modified version of your script:
class DetachableTabWidget(QTabWidget):
# ...
#pyqtSlot(int, QPoint)
def detachTab(self, index, point):
print("detachTab")
name = self.tabText(index)
icon = self.tabIcon(index)
if icon.isNull():
icon = self.window().windowIcon()
contentWidget = self.widget(index)
contentWidgetRect = contentWidget.frameGeometry()
tablewidgetA = contentWidget.findChild(QTableWidget)
detachedTab = self.DetachedTab(tablewidgetA.model())
detachedTab.setWindowTitle(name)
detachedTab.setWindowIcon(icon)
detachedTab.setObjectName(name)
detachedTab.setGeometry(contentWidgetRect)
detachedTab.move(point)
detachedTab.exec_()
class DetachedTab(QDialog) :
onCloseSignal = pyqtSignal(QWidget,type(''), QIcon)
def __init__(self, model, parent=None) :
print("DetachedTab")
super().__init__()
layout = QVBoxLayout(self)
table = QTableView()
table.setModel(model)
layout.addWidget(table)
table.show()
With this code you can modify the "child" window table data, and it will always synchronize the source table widget.

How can I modify a QtableWidget which is on a QMainWindow from a QGraphicsView method?

Essentially I created a QMainWindow which has a Splitter which, in turn, has on its left side a QTableWidget and on the right side a QGraphicsView.
I have also created a method to zoom in and out in the QGraphicsView. Now, I want to resize the height of the rows depending on the amount of zoom provided by the user.
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
# Main characteristics of the window
self.setGeometry(50, 50, 1000, 700)
#User Interface
self.initUI()
def initUI(self):
#Creation of table and timeline splitter
self.table_and_view_splitter = QtWidgets.QSplitter()
self.table_and_view_splitter.setOrientation(QtCore.Qt.Horizontal)
#Creation of metadata table
self.create_table()
self.table_and_view_splitter.addWidget(self.table)
#Creation of View and Scene for timeline
self.create_view()
self.table_and_view_splitter.addWidget(self.view)
# Creation of vertical splitter
self.vertical_splitter = QtWidgets.QSplitter()
self.vertical_splitter.setOrientation(QtCore.Qt.Vertical)
self.vertical_splitter.insertWidget(1, self.table_and_view_splitter)
# Choosing the sizes of the upper and lower widgets of the Qsplitter
self.sizes_list = [100, 5000]
self.vertical_splitter.setSizes(self.sizes_list)
self.setCentralWidget(self.vertical_splitter)
def create_table(self):
self.table = QTableWidget()
self.table.setColumnCount(3)
self.table.setRowCount(100)
for i in range(self.table.rowCount()):
self.table.setRowHeight(i, 10)
def create_view(self):
self.view = viewFor()
self.scene = QtWidgets.QGraphicsScene()
self.scene.addEllipse(1, 1, 10, 10)
self.view.setScene(self.scene)
class viewFor(QGraphicsView):
def __init__(self):
super(viewFor, self).__init__()
self.drag = False
self.setTransformationAnchor(self.NoAnchor)
def wheelEvent(self, event):
self.setTransformationAnchor(self.AnchorUnderMouse)
zoom_in_factor = 1.1
zoom_out_factor = 1 / zoom_in_factor
# Save the scene pos
old_position = self.mapToScene(event.pos())
if QApplication.keyboardModifiers() == Qt.ControlModifier:# CTRL + Scroll -> X and Y Zoom
# Zoom
if event.angleDelta().y() > 0:
zoom_factor = zoom_in_factor
else:
zoom_factor = zoom_out_factor
self.scale(zoom_factor, zoom_factor)
#HERE I WANT TO RESIZE THE ROWS HEIGHT ACCORDING TO THE zoom_factor
# Get the new position
new_position = self.mapToScene(event.pos())
# Move scene to old position
delta = new_position - old_position
self.translate(delta.x(), delta.y())
else:# Only Scroll -> only X Zoom
# Zoom
if event.angleDelta().y() > 0:
zoom_factor = zoom_in_factor
else:
zoom_factor = zoom_out_factor
self.scale(zoom_factor, 1)
# Get the new position
new_position = self.mapToScene(event.pos())
# Move scene to old position
delta = new_position - old_position
self.translate(delta.x(), delta.y())
app = QApplication([])
foo = MyWindow()
foo.show()
sys.exit(app.exec_())
You need to emit a signal whenever the scale is changed, and QHeaderView.setDefaultSectionSize() for the vertical header. Note that you should probably use setSectionResizeMode(QHeaderView.Fixed) to avoid user resizing (or just leave it to Interactive, but certainly don't use Stretch or ResizeToContents).
You should obviously ensure that the range is valid or find your own algorithm (that sanitizes the value to a valid range that has at least a minimum of 1).
In this case I used the default original value and multiplied it using the scale factor of the view's transformation (see QTransform > rendering graphics about the meaning of the transformation matrix).
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
# Main characteristics of the window
self.setGeometry(50, 50, 1000, 700)
#User Interface
self.initUI()
self.view.scaleChanged.connect(self.resizeRows)
self.defaultSize = self.table.verticalHeader().defaultSectionSize()
def resizeRows(self, scale):
self.table.verticalHeader().setDefaultSectionSize(scale * self.defaultSize)
class viewFor(QtWidgets.QGraphicsView):
scaleChanged = QtCore.pyqtSignal(float)
# ...
def wheelEvent(self, event):
self.setTransformationAnchor(self.AnchorUnderMouse)
zoom_in_factor = 1.1
zoom_out_factor = 1 / zoom_in_factor
# Save the scene pos
old_position = self.mapToScene(event.pos())
if event.modifiers() == QtCore.Qt.ControlModifier:# CTRL + Scroll -> X and Y Zoom
# Zoom
if event.angleDelta().y() > 0:
zoom_factor = zoom_in_factor
else:
zoom_factor = zoom_out_factor
self.scale(zoom_factor, zoom_factor)
# emit the signal based on the transformation scale factor
self.scaleChanged.emit(self.transform().m11())
# ...
Note that you don't need to use QApplication.keyboardModifiers, as you can access modifiers() of all keyboard/mouse events.

Is there any way to check whether the QRect contains QGraphicspathitem inside its boundary in pyqt5?

I have an QGraphicspathitem in the scene. If clicked on the scene, I will get the rect of that grid. Now what I need is,to check whether the QRect contains QGraphicspathitem inside its boundary
Check if a QPainterPath P is within a rectangle R (without rotation) if the minimum rectangle containing P is contained in R, and for this you must use boundingRect(). To know if a rectangle (R1) is contained in another (R2) it is enough knowing that the union of both rectangle is equal to R2. Considering that the solution is:
import math
import random
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self._scene = QtWidgets.QGraphicsScene(QtCore.QRectF(0, 0, 400, 400), self)
self.setScene(self._scene)
self._rect = QtCore.QRectF(40, 40, 100, 100)
rect_item = self._scene.addRect(self._rect)
rect_item.setPen(QtGui.QPen(QtGui.QColor("green")))
# create QGraphicsPathItems
radius, number_of_sides = 40, 5
for i in range(4):
pos = QtCore.QPointF(*random.sample(range(0, 200), 2))
center = QtCore.QPointF(*random.sample(range(0, 200), 2))
poly = QtGui.QPolygonF()
for j in range(number_of_sides + 1):
angle = 2 * math.pi * j / number_of_sides
poly << (
center + radius * QtCore.QPointF(math.cos(angle), math.sin(angle))
)
path = QtGui.QPainterPath()
path.addPolygon(poly)
path_item = QtWidgets.QGraphicsPathItem(path)
path_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
path_item.setPos(pos)
self._scene.addItem(path_item)
timer = QtCore.QTimer(self, timeout=self.on_timeout, interval=60)
timer.start()
def on_timeout(self):
for item in self._scene.items():
if isinstance(item, QtWidgets.QGraphicsPathItem):
color = (
QtGui.QColor("blue")
if self.verify_if_qgraphicspathitem_inside_qrectf(item, self._rect)
else QtGui.QColor("red")
)
item.setBrush(QtGui.QBrush(color))
def verify_if_qgraphicspathitem_inside_qrectf(self, item, rect):
"""
Check if a QGraphicsPathItem(item) is inside a rectangle(rect)
"""
path = item.mapToScene(item.path())
return rect == path.boundingRect().united(rect)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = GraphicsView()
w.show()
w.resize(640, 480)
sys.exit(app.exec_())