Issue
I include the entire code - the problem is (I believe!) with the function adjust_gx on line 145-153..
The problem is that the QGraphicsScene is NOT scaling correctly to the QGraphicsView. I'm hoping to use a set of standard shapes and fill a grid, which resizes either according to the window, or according to the cell size value. - but it's rendering very small, or resizes in a miserable manner.
I have found ways of scaling it up, but then it goes out of sync completely when 'size' is varied (ranging between 16 and 120) or the window bounds are changed.
Most of the advice I have found is all about using scene.itemsBoundingRect()- which I believe I am using correctly.
You can see just how tiny the graphic is being drawn in the screenshot.
from itertools import product
from math import ceil
from random import choices
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QPen, QPainterPath
from PyQt5.QtWidgets import QGraphicsScene
from PyQt5.QtCore import Qt, QRect, QPointF
class Art:
normal = 100
w_off = 2
line = normal / 2
t_l = QPointF(-line + w_off, -line + w_off)
t_r = QPointF(+line - w_off, -line + w_off)
b_r = QPointF(+line - w_off, +line - w_off)
b_l = QPointF(-line + w_off, +line - w_off)
@staticmethod
def _poly(edges):
pt = [Art.t_r, Art.b_r, Art.b_l, Art.t_l]
path = QPainterPath()
path.moveTo(Art.t_l)
for i in range(4):
path.lineTo(pt[i]) if edges[i] else path.moveTo(pt[i])
return path
def __init__(self):
self.x = 1
self.y = 1
self.z = 1
self.cells = {}
self.scenes = []
self.polygons = {x: self._poly(x) for x in product((False, True), repeat=4)}
def reset(self, x, y, z):
self.x = x
self.y = y
self.z = z
self.cells = {tuple((ix, iy, iz)): tuple(choices([True, False], k=4))
for ix in range(self.x)
for iy in range(self.y)
for iz in range(self.z)
}
for scene in self.scenes:
scene.clear()
def draw(self):
pen = QPen(Qt.black, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
for x, y, z in self.cells:
cx1 = x * Art.normal
cy1 = y * Art.normal
shape = self.polygons[self.cells[x, y, z]]
self.scenes[z].addPath(shape.translated(QPointF(cx1, cy1)), pen)
class MyDialog(QtWidgets.QDialog):
"""
Is the main application window, which is made of two parts:
One one side is the pane of tabs which display the maze
On the other are the configurable values of the maze (levels)
"""
def __init__(self):
self.pane = None
self.form = None
self.horizontal_layout = None
super().__init__()
def resizeEvent(self, event):
self.pane.resize()
super().resizeEvent(event)
def setup(self):
self.pane = Pane(self)
self.form = Form(self)
self.setWindowTitle("Configure")
self.setObjectName("Dialog")
size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
size_policy.setHorizontalStretch(0)
size_policy.setVerticalStretch(0)
size_policy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
self.setSizePolicy(size_policy)
self.horizontal_layout = QtWidgets.QHBoxLayout(self)
self.horizontal_layout.setObjectName("horizontal_layout")
self.pane.setup(self.horizontal_layout)
self.form.setup(self.horizontal_layout)
self.pane.connect_form(self.form)
class Pane:
def __init__(self, parent):
self.parent = parent # This is the widget.
self.layout = None # This is the parent's layout.
self.form = None # This is the form used to control things.
self.tab_widget = None # This is the tab widget, which can be fully replaced.
# Everything below are default values.
self.offset = 12
self.demi = self.offset // 2
self.tmp_idx = 0
self.levels = 4
self.width = 3
self.height = 3
self.cell_size = 100
self.art = None
self.level_tabs = []
self.level_gx = []
def setup(self, parent_layout):
self.layout = parent_layout
self.art = Art()
tab_widget_dim = QRect(0, 0, 360, 380) # This is the size of the initial tab_widget
self.set_tab_widget(tab_widget_dim)
def connect_form(self, form):
self.form = form
self.form.set_values({'Size': self.cell_size, 'Levels': self.levels})
self.form.set_listen({'Size': self.resize, 'Levels': self.re_depth})
def make_tab_widget(self):
self.tab_widget = QtWidgets.QTabWidget(self.parent)
self.tab_widget.setMinimumSize(QtCore.QSize(360, 380))
self.tab_widget.setTabPosition(QtWidgets.QTabWidget.North)
self.tab_widget.setObjectName("tab_widget")
def reset_tab_widget(self):
self.tmp_idx = 0
prev_widget = self.tab_widget
self.make_tab_widget()
if prev_widget:
self.tmp_idx = prev_widget.currentIndex()
self.parent.horizontal_layout.replaceWidget(prev_widget, self.tab_widget)
prev_widget.clear()
prev_widget.close()
else:
self.parent.horizontal_layout.addWidget(self.tab_widget)
if self.art:
for lt in self.level_tabs:
lt.close()
self.level_tabs = []
for gx in self.level_gx:
gx.close()
self.level_gx = []
self.art.scenes = []
def adjust_gx(self, gv, scene, frame):
# Resize the graphics port to fit the outer frame. (GUI pixels)
gv.resize(frame.width(), frame.height())
# Seems to make no difference.
boundary = scene.itemsBoundingRect()
gv.fitInView(boundary, Qt.KeepAspectRatio)
scene.setSceneRect(boundary)
# Also tried the below...
# gv.fitInView(scene.sceneRect(), Qt.KeepAspectRatio)
def set_tab_widget(self, tab_widget_dim):
self.reset_tab_widget() # initialises / replaces a QTabWidget into the horizontal_layout
for level in range(self.levels):
tab = QtWidgets.QWidget()
tab.setObjectName(f"T{level}")
self.tab_widget.addTab(tab, f"{level + 1}")
self.level_tabs.append(tab)
scene = QGraphicsScene()
self.art.scenes.append(scene)
tab.resize(tab_widget_dim.width(), tab_widget_dim.height())
gv = QtWidgets.QGraphicsView(scene, tab)
gv.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
gv.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
gv.setMinimumSize(QtCore.QSize(340, 340))
gv.setObjectName(f"Gx{level}")
self.level_gx.append(gv)
# self.adjust_gx(gv, scene, tab_widget_dim)
self.art.reset(self.width, self.height, self.levels)
self.art.draw()
for idx in range(self.levels):
self.adjust_gx(self.level_gx[idx], self.art.scenes[idx], tab_widget_dim)
self.tab_widget.setCurrentIndex(self.tmp_idx % self.levels)
def resize(self):
if self.tab_widget:
self.tab_widget.hide()
idx = self.tab_widget.currentIndex()
dt = QRect(self.level_tabs[idx].frameGeometry())
self.cell_size = self.form.get_value('Size')
self.width = ceil((dt.width() - self.offset) // self.cell_size)
self.height = ceil((dt.height() - self.offset) // self.cell_size)
self.form.set_texts({'Width': f"{self.width}", 'Height': f"{self.height}"})
self.set_tab_widget(dt)
self.tab_widget.show()
self.parent.update()
def re_depth(self):
self.levels = self.form.get_value('Levels')
self.resize()
class Form(QtWidgets.QWidget):
def __init__(self, parent):
self.parent = parent
self.form = None
self.label_text = ('Levels', 'Size', 'Width', 'Height')
self.minimums = (1, 16)
self.maximums = (20, 320)
self.labels = []
self.fields = {}
super().__init__(self.parent)
def setup(self, parent_layout):
self.form = QtWidgets.QFormLayout(self)
self.form.setObjectName("form")
self.setMaximumSize(QtCore.QSize(160, 200))
self.setMinimumSize(QtCore.QSize(160, 200))
# Create the fields within the form layout
for field in range(4):
if field < 2:
ui = QtWidgets.QSpinBox(self)
ui.setMinimum(self.minimums[field])
ui.setMaximum(self.maximums[field])
ui.setMinimumSize(QtCore.QSize(60, 0))
ui.setObjectName(f"f_{field}")
else:
ui = QtWidgets.QLabel(self)
ui.setObjectName(f"f_{field}")
ui.setText(f"{0}")
self.form.setWidget(field + 1, QtWidgets.QFormLayout.FieldRole, ui)
self.fields[self.label_text[field]] = ui
label = QtWidgets.QLabel(self)
label.setObjectName(f"label_{field}")
label.setText(self.label_text[field])
self.form.setWidget(field + 1, QtWidgets.QFormLayout.LabelRole, label)
self.labels.append(label)
parent_layout.addWidget(self)
def get_value(self, name):
return self.fields[name].value()
def set_values(self, config):
for k in config.keys():
self.fields[k].setValue(config[k])
def set_texts(self, config):
for k in config.keys():
self.fields[k].setText(config[k])
def set_listen(self, config):
for k in config.keys():
self.fields[k].valueChanged.connect(config[k])
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
dialog = MyDialog()
dialog.setup()
dialog.show()
result = app.exec_()
Solution
A possible solution is to do the fitview a moment later because for efficiency reasons Qt does not immediately update the properties:
# ... self.art.reset(self.width, self.height, self.levels) self.art.draw() def adjust(): for idx in range(self.levels): self.adjust_gx(self.level_gx[idx], self.art.scenes[idx], tab_widget_dim) QtCore.QTimer.singleShot(0, adjust) self.tab_widget.setCurrentIndex(self.tmp_idx % self.levels) # ...
Answered By - eyllanesc
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.