Kivy: drawing not updating when changing line points - python

I guess my question is pretty much summed up in the title.
I am using an update call (similar to the one in the Pong tutorial). Within this call I update the points of a line. Though I can check that the points are indeed being updated, the actual line drawing is not.
I'll put some of the code up here:
class GraphInterface(Widget):
node = ObjectProperty(None)
def update(self, dt):
for widget in self.children:
if isinstance(widget, GraphEdge) and widget.collide_widget(self):
widget.check_connection()
class GraphEdge(Widget):
r = NumericProperty(1.0)
#determines if edge has an attached node
connected_point_0 = Property(False)
connected_point_1 = Property(False)
#provides details of the attached node
connected_node_0 = Widget()
connected_node_1 = Widget()
def __init__(self, **kwargs):
super(GraphEdge, self).__init__(**kwargs)
with self.canvas:
Color(self.r, 1, 1, 1)
self.line = Line(points=[100, 200, 200, 200], width = 2.0, close = True)
def snap_to_node(self, node):
if self.collide_widget(node):
if (self.connected_point_1 is False):
print "collision"
self.connected_point_1 = True
self.connected_node_1 = node
del self.line.points[-2:]
self.line.points[-2:]+=node.center
self.size = [math.sqrt(((self.line.points[0]-self.line.points[2])**2 + (self.line.points[1]-self.line.points[3])**2))]*2
self.center = ((self.line.points[0]+self.line.points[2])/2,(self.line.points[1]+self.line.points[3])/2)
return True
pass
The idea is to check for collisions initially, and once a collision has been made, I attach the line to this node widget. The points are then update as I move the node around. However right now although the points are updated, the drawing of the line is not.
If you need anymore code or information please ask.

del self.line.points[-2:]
self.line.points[-2:]+=node.center
These lines bypass operations that set the property, so the VertexInstruction doesn't know anything has changed and doesn't redraw itself.
They're a bit strange anyway, it would be simpler to just write:
self.line.points = self.line.points[:-2] + node.center
This would also update the instruction graphics, because you set the property directly rather than only modifying the existing list.

Related

How can I print a QTableWidget by clicking on QPushButton

I have an interface with two tab: in the first one i ask the user to enter parameters and in the second one i want to print the following QTableWidget.
So basically on the first tab i have a QPushButton that i called process and normally, when i push on it , i want to send the information to the second Tab.
Right now i just tried to show a new window with the QTableWidget and the good parameters :
class Parameters(QWidget):
def __init__(self):
super(Parameters, self).__init__()
self.matrixsize = QLineEdit()
bouton = QPushButton("define matrix_size")
bouton.clicked.connect(self.appui_bouton)
self.halfmatrix = QCheckBox()
self.halfmatrix.toggled.connect(self.on_checked)
self.define_matrix_size = QGroupBox('Define Parameters')
layout = QGridLayout()
layout.addWidget(self.matrixsize, 0, 0, 1, 1, )
layout.addWidget(bouton, 0, 1, 1, 1)
layout.addWidget(QLabel('select half size mode'
), 1, 0, 1, 1)
layout.addWidget(self.halfmatrix, 1, 1, 1, 1)
self.define_matrix_size.setLayout(layout)
process = QPushButton('process')
process.clicked.connect(self.process)
self.matrix = QTableWidget()
self.layout = QGridLayout()
self.layout.addWidget(self.define_matrix_size)
self.layout.addWidget(matrix)
self.layout.addWidget(process)
self.setLayout(self.layout)
def matrix_size(self):
if self.matrixsize.text() == "":
return 0
else:
return int(self.matrixsize.text())
def appui_bouton(self):
taille = self.matrixsize()
self.matrix.deleteLater()
if self.halfmatrix.isChecked():
self.on_checked()
else:
self.matrix = QTableWidget()
self.matrix.setColumnCount(taille)
self.matrix.setRowCount(taille)
self.layout.addWidget(self.matrix)
self.update()
self.setLayout(self.layout)
def keyPressEvent(self, qKeyEvent):
print(qKeyEvent.key())
if qKeyEvent.key() == Qt.Key_Return or qKeyEvent.key() == Qt.Key_Enter:
self.appui_bouton()
else:
super().keyPressEvent(qKeyEvent)
def on_checked(self):
taille = self.matrixsize()
if taille == 0:
pass
else:
if self.halfmatrix.isChecked():
size = int(taille / 2)
self.matrix.deleteLater()
self.matrix = QTableWidget()
self.matrix.setColumnCount(size)
self.matrix.setRowCount(size)
self.layout.addWidget(self.matrix, 3, 0, 20, 4)
self.update()
self.setLayout(self.layout)
else:
self.appui_bouton()
def process (self):
layout = QHBoxLayout()
test = self.matrix
test.setLayout(layout)
test.show()
So in order to clarify what i said: i have a Window on which you get some parameters (size,...) , when you select those parameters, let's say you take matrixsize==5, then a 5x5 table is added to the window. This table can be after this fill by others parameters (i cut them on the code) by a system of drag and drop.
So now that i got a built table, i want to be able to open a new window with just the table by clicking on the ''process'' button.
So i don't want a dynamical table, i just want a table that keeps the same property (for instance if the matrix has dragonly enable then the new matrix should have the same) . I want to keep every information containing in the cells
I hope i am enoughly clear that is my first time asking questions (after many times reading some answers of course^^)
thanks for your answer and advice !
You can just create a new QTableWidget with no parent (which makes it a top level window), and then show it:
class Parameters(QWidget):
# ...
def process(self):
rows = self.matrix.rowCount()
columns = self.matrix.columnCount()
self.newTable = QTableWidget(rows, columns)
for row in range(rows):
for column in range(columns):
source = self.matrix.item(row, column)
if source:
self.newTable.setItem(row, column, QTableWidgetItem(source))
self.newTable.show()
Note that I created the new table as an instance attribute. This allows to avoid the garbage collection in case it was a local variable (resulting in the widget showing and disappearing right after), but has the unfortunate effect that if you click on the process button again and a window already exists, it gets deleted and "overwritten" with a new window. If you want to have more process windows at the same time, you could add them to a list:
class Parameters(QWidget):
def __init__(self):
super(Parameters, self).__init__()
# ...
self.processTables = []
def process(self):
rows = self.matrix.rowCount()
columns = self.matrix.columnCount()
# note that now "newTable" is *local*
newTable = QTableWidget(rows, columns)
self.processTables.append(newTable)
# ...
Some suggestions about your code:
there's absolutely no need to create a new table each time you want to change its size; just use setRowCount and setColumnCount on the existing one, and if you don't want to keep previous values, use clear();
don't use two functions that do almost the same things (appui_bouton and on_checked) and call each other, just use one function that checks for both aspects;
don't call update() unnecessarily: when you change the properties of a widget (or add a new widget to a layout) update is called already; while it's not an actual issue (Qt automatically manages when updates actually happen, avoiding repainting if not necessary), calling it just adds unnecessary noise to your code;
be more careful when adding widgets to a grid layout (I'm referring to the code on on_checked): don't use the rowSpan and columnSpan if not required; also, using a value that high is completely useless, as there are no other widgets in that row, and there's actually only one column in that layout; also, don't call setLayout() again;
if you need a numerical value, then use a QSpinBox, not a QLineEdit.
The function to update the existing table can be rewritten more easily, and you should connect both the button and the checkbox to it:
class Parameters(QWidget):
def __init__(self):
super(Parameters, self).__init__()
self.matrixsize = QSpinBox()
bouton = QPushButton("define matrix_size")
bouton.clicked.connect(self.appui_bouton)
self.halfmatrix = QCheckBox()
self.halfmatrix.toggled.connect(self.appui_bouton)
# ...
def appui_bouton(self):
taille = self.matrixsize.value()
if self.halfmatrix.isChecked():
taille //= 2
if not taille:
return
self.matrix.setColumnCount(taille)
self.matrix.setRowCount(taille)

Procedural Generation Using Classes Pygame

I'm in the process of creating a basic game in pygame at the moment, and one part of that is the procedural generation of new areas as you go off the screen. As a test, I'm looking to generate an object once per area by defining its variables, and then save that area's object within the class for if you come back to it later. Here's what I have at the moment:
#area_gen is set to "true" if you move to a new area
#swidth and sheight are set to the size of the screen
#x_area and y_area are defined as you change areas, acting as sector coordinates
#Red is defined in globals
class areas:
def __init__(self, coords):
self.coordinates = coords
self.generated = False
def gen_objects(self):
if not self.generated:
self.objs = []
obj_type = "test object"
center_x = random.randrange(105, swidth-25)
center_y = random.randrange(25, swidth - 175)
self.objs.append([obj_type, center_x, center_y])
self.generated = True
#Within The Game Loop
if area_gen == "true":
coords = str(str(x_area) + " " + str(y_area))
area = areas(coords)
area.gen_objects()
for thing in area.objs:
if thing[0] == "test object":
pygame.draw.rect(screen, Red, (thing[1], thing[2], 250, 250))
#Bottom of the Game Loop
area_gen = "false"
What I thought the self.generated variable would do was stop the new object generation if one already existed, but that doesn't seem to be working. The square still generates at a new location even if the area has already been visited. My knowledge on classes is relatively limited, so I'm a bit stuck as to where to go from here.
Pass area_gen into the constructor for the areas class, and set self.generated = area_gen. Does this work? I can't see enough of your code to know, to be honest.

Having a label fade out in kivy

So I want to display a label if someone tries to click play and there is no save file made yet. Then I want it to fade out. The while loop works, reducing the value of alpha to 0. And it displays the label as long as I don't have the self.remove_widget(no_save) added in but then it just stays as a solid label. Any help would be appreciated. Or is there an easier way to do this?
class StartMenu(Screen):
def check_save(self):
global save_state
if save_state == None:
color = (0,1,0,1)
while color[3] > 0:
no_save = Label(text='No save file found. Please press New Game', color=color)
self.add_widget(no_save)
color = color [:3] + (color[3] - (.1),)
time.sleep(.1)
self.remove_widget(no_save)
Rather than doing the fade out yourself, why not use the built in Animation functionality? Try something like this. I would also suggest moving save_state from the global realm to your class, and instead of creating and destroying the label every run, I would create at initialization and simply hide or show it as it becomes necessary.
class StartMenu(Screen):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.save_state = None
no_save = Label('No save file found. Please press new game.', hidden=True)
self.add_widget(no_save)
def check_save(self):
if not self.save_state:
self.no_save.hidden = False
def hide_label(w): w.hidden = True
Animation(opacity=0, duration=1, on_complete=hide_label).start(self.no_save)
Quick shoutout to zeeMonkeys for pointing out the Animation solution in the comments before I did.

Is storing pyQT widgets in a list bad form?

So I am building I program that manages a bunch of custom slider widgets. Currently, I have a slider_container(class) that holds a list of slider objects(class). These slider objects are then inserted into the layout in the main window. This has been working well while I was only adding and moving the position of the sliders up and down. But when I try to delete the sliders, everything goes bad. When ever there the list of slider is manipulated (add, move or delete a slider), the clear and rebuild functions are called in the main window as seen below.
def clear_layout(self, layout):
print "Cleared Layout..."
while layout.count() > 0:
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
def rebuild_slider_display(self):
""" Delete the old sliders, then add all the new sliders """
self.clear_layout(self.slider_layout)
print "Rebuild layout..."
print len(self._widgets.widgets)
for i, widget in enumerate(self._widgets.widgets):
print widget.slider_name, " ", i
self.slider_layout.insertWidget(i, widget)
print "Layout widget count: ", self.slider_layout.count()
Currently I am running into this error on this line "self.slider_layout.insertWidget(i, widget)"
RuntimeError: wrapped C/C++ object of type SliderWidget has been deleted
My hunch is that storing the actual widget in the widget container is bad form. I think what is happening when I deleteLater() a widget, is that it isnt just deleting a widget from the list, it actually deletes the widget class that was store in the widget container itself.
Hopefully that is explained clearly, thanks for your help in advance.
Edit:
Here is the widget class:
class SliderWidget(QWidget, ui_slider_widget.Ui_SliderWidget):
""" Create a new slider. """
def __init__(self, name, slider_type, digits, minimum, maximum, value, index, parent=None):
super(SliderWidget, self).__init__(parent)
self.setupUi(self)
self.slider_name = QString(name)
self.expression = None
self.accuracy_type = int(slider_type)
self.accuracy_digits = int(digits)
self.domain_min = minimum
self.domain_max = maximum
self.domain_range = abs(maximum - minimum)
self.numeric_value = value
self.index = index
#self.name.setObjectName(_fromUtf8(slider.name))
self.update_slider_values()
self.h_slider.valueChanged.connect(lambda: self.update_spinbox())
self.spinbox.valueChanged.connect(lambda: self.update_hslider())
self.edit.clicked.connect(lambda: self.edit_slider())
# A unique has for the slider.
def __hash__(self):
return super(Slider, self).__hash__()
# How to compare if this slider is less than another slider.
def __lt__(self, other):
r = QString.localAware.Compare(self.name.toLower(), other.name.toLower())
return True if r < 0 else False
# How to compare if one slider is equal to another slider.
def __eq__(self, other):
return 0 == QString.localAwareCompare(self.name.toLower(), other.name.toLower())
And here is the actually creation of the widget in the widget container:
def add_slider(self, params=None):
if params:
new_widget = SliderWidget(params['name'], params['slider_type'], params['digits'], params['minimum'],
params['maximum'], params['value'], params['index'])
else:
new_widget = SliderWidget('Slider_'+str(self.count()+1), 1, 0, 0, 50, 0, self.count())
#new_widget.h_slider.valueChanged.connect(self.auto_save)
#new_widget.h_slider.sliderReleased.connect(self.slider_released_save)
new_widget.move_up.clicked.connect(lambda: self.move_widget_up(new_widget.index))
new_widget.move_down.clicked.connect(lambda: self.move_widget_down(new_widget.index))
self.widgets.append(new_widget)
Thanks for all the help!
The problem I was having was with the way I cleared the layout. It is important to clear the layout from the bottom to the top as seen below.
for i in reversed(range(layout.count())):
layout.itemAt(i).widget().setParent(None)

Animating a custom QGraphicsItem with QPropretyAnimation

I want to make a car follow a path, so I tried animating a custom QGraphicsItem (describing a 'car') using QPropreties, starting by an example given on PySide documentation :
self.anim = QtCore.QPropertyAnimation(self.car, "geometry")
self.anim.setDuration(4000);
self.anim.setStartValue(QtCore.QRect(0, 0, 100, 30));
self.anim.setEndValue(QtCore.QRect(250, 250, 100, 30));
self.anim.start();
self.car here is an instance of Car, a class that inherits from QGraphicsItem and QObject ( EDIT Changed this, see EDIT 2):
class Car(QtGui.QGraphicsItem, QtCore.QObject):
def __init__(self, map = None, ....):
super(Car, self).__init__()
self.map = map
....
I always get the same error code when executing this :
QPropertyAnimation::updateState (geometry): Changing state of an animation without target
This is weird as the target is really set ! I tried putting setTargetObject ( see the documentation on PySide ) but it didn't change anything.
Any idea about where that could come from ?
EDIT : Something that could help -> I tried putting those three lines and the result shows that the object is not taken into account :
print self.car
self.anim.setTargetObject(self.car)
print self.anim.targetObject()
Result
<engine.Car(this = 0x43f9200 , parent = 0x0 , pos = QPointF(0, 0) , z = 0 , flags = ( ItemIsMovable | ItemIsSelectable ) ) at 0x1de0368>
None
EDIT 2 : Changing the class inheritance to QGraphicsObject seems to have solved the 'no target' problem ! But I have a different error now (using a custom defined property 'pos'):
QPropertyAnimation::updateState (pos, Car, ): starting an animation without end value
I defined my property that way :
def setPos(self, pos):
self.x = pos[0]
self.y = pos[1]
self.update()
def readPos(self):
return (self.x, self.y)
pp = QtCore.Property('tuple', readPos, setPos)
If you just need to animate position, you can animate GraphicsItem.pos rather than defining your own position functions (unless you want the car to have a position within the QGraphicsItem itself.
As for 'starting an animation without end value', make sure you:
Call setEndValue on the animation.
Pass in the correct type to setEndValue (e.g. your current implementation would require a tuple, using GraphicsItem.pos would require a QPoint).
The solution was to first change the inheritance from QGraphicsItem and QObject to QGraphicsObject. Then, the second error I did is I didn't define my property (x and y) correctly.
I ended up re-implementing my class to use QGraphicsObject's "pos" property and give it the correct start and end value (tuples).

Categories

Resources