How does the 'with' statement work in kivy? - python

I have been playing with kivy, and I saw this:
with self.canvas:
Color(1, 1, 0) # <--- no assignment
d = 30.
Ellipse(pos=(touch.x - d / 2, touch.y - d / 2), size=(d, d)) # <--- no assignment
I don't quite understand how it works. I was expecting to see something like:
with self.canvas as c:
c.color = Color(1, 1, 0)
c.shape = Ellipse()
What am I missing?

with some_canvas sets an internal Kivy variable to that canvas. When canvas instructions are created they check if that variable is set to a canvas, and if so they automatically add themselves to it.
If you want to trace through how this works you can find the context entry function here, and the code that automatically adds instructions to a canvas when instantiated here.
I don't quite understand how it works. I was expecting to see something like:
with self.canvas as c:
c.color = Color(1, 1, 0)
c.shape = Ellipse()
In this case the with context wouldn't really be doing anything. If you want to explicitly manipulate the canvas you can do it directly without a context manager, e.g. self.canvas.add(Color(1, 1, 0)).
That said, the way you've written this may indicate a misunderstanding: a canvas doesn't have a specific colour as you've indicated with c.color, rather it's a list of instructions to apply in order. There could be many Color instructions, with different numbers of other instructions (e.g. representing different shapes) in between.

Related

Two consecutive animations synchronised with one longer animation

In particular, I would like to smoothly zoom out and then zoom in all the while an object rotates. I cannot seem to control runtimes separately and I couldn't figure out how to use LaggedStart or Succession to achieve this. I also couldn't make an updater work – the width does not change at all. I'm including the last try on the updater.
class Rectangles(MovingCameraScene):
def construct(self):
rect1 = Rectangle(height=4.2, width=9.3)
staticobj = Rectangle(height=8,width=2,fill_color=BLUE).shift(RIGHT*7)
self.add(rect1,staticobj)
self.camera.frame.set(width=20)
x = ValueTracker(-.99)
def zoom_level(inp):
inp.set(width=math.exp(10 / (x.get_value() ** 2 - 1) + 20))
self.camera.frame.add_updater(zoom_level)
self.play(Rotate(rect1, angle=PI / 2, about_point=ORIGIN),x.animate.set_value(0.99),run_time=5)
self.wait(1)
To whoever stumbles upon this: the solution I came up with in the end was to use UpdateFromAlphaFunc.
class Rectangles(MovingCameraScene):
def construct(self):
rect1 = Rectangle(height=4.2, width=9.3)
staticobj = Rectangle(height=8,width=2,fill_color=BLUE).shift(RIGHT*7)
self.add(rect1,staticobj)
self.camera.frame.set(width=20)
def zoom_level(inp,alpha):
inp.set(width=20+5-20*(alpha-0.5)**2)
self.play(Rotate(rect1, angle=PI / 2, about_point=ORIGIN),
UpdateFromAlphaFunc(self.camera.frame,zoom_level),run_time=5)
self.wait(1)

Tkinter: Configuring multiple buttons with images

I have a function that takes the names of image files and creates a grid of them as buttons using the image attribute, which is where the issue arises, since I need the button object to create the image due to another function of mine fit_image() (which in this case fits the image inside the object completely, hence full=True). The result of running this without adding the images to these buttons is fine and all the buttons are clickable:
self.image_buttons = {}
count = 0
# goes by column
for y in range(height_divisor):
# row
for x in range(width_divisor):
self.image_buttons[count] = tk.Button(self.preview_frame)
self.image_buttons[count].place(
relx=0 + (1 / width_divisor * x),
rely=0 + (1 / height_divisor * y),
relwidth=1 / width_divisor,
relheight=1 / height_divisor,
anchor="nw",
)
# self.current_image = fit_image(
# Image.open(names_of_files[count]), self.image_buttons[count], full=True
# )
# self.image_buttons[count].configure(image=self.current_image)
count += 1
print(self.image_buttons)
Result of print statement:
{0: <tkinter.Button object .!toplevel2.!frame2.!button>, 1: <tkinter.Button object .!toplevel2.!frame2.!button2>, 2: <tkinter.Button object .!toplevel2.!frame2.!button3>, 3: <tkinter.Button object .!toplevel2.!frame2.!button4>}
However, once I uncomment this code, only the last button is clickable and does have the image on it, but all the others are blank and not clickable buttons. I have tried putting the commented (image configuration) lines in a separate for loop afterwards to go through and configure each button to no avail. Though I've previously had this work and even tried combing through my repo's commits (nothing before Aug 8th should be relevant) to see how it worked before, it must've been working then I most likely broke it before committing.
This was solved thanks to #jasonharper 's comment by changing these two lines and adding a new dictionary:
self.image_buttons, self.images = {}, {}
#for loops here
self.images[count] = fit_image(
Image.open(names_of_files[count]), self.image_buttons[count], full=True
)
self.image_buttons[count].configure(image=self.images[count])

How do I get indices around certain values in an array

So I came accros this tiny but tricky problem.
I have an array of indices of error_images, something like this:
error_frames = [15,27,34,204,205]
Now I am using a for loop to iterate over these frames and add 10 frames in prior and after the erroneous frames in order to get a sequence, so that in the end, the array is more populated. I also removed duplicates (excuse MATLAB writing):
error_sequence = [5:44,194:215]
Now the tricky part:
In Order to show the sequence, I load every image and show it for a certain time using OpenCV cv2.imshow. When actually encountering an error, I want to increase the pause between shown images ('slow motion') and have a special overlay. This looks like this, where frames is a collection of my pictures of the scene:
for x in error_sequence:
if x in error_frames:
cv2.imshow('Sequence', frames[x])
cv2.putText(frames[x], "Error!", (50, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 3)
cv2.waitKey(100)
else:
cv2.imshow('Sequence', frames[x])
cv2.waitKey(50)
The problem is, that this "one-frame" overlay is way too fast and I want to slow down more frames, lets say 3 before and after the encountered error.
Is there some kind of function, that lets me do that? Something like:
if x+-3 in error_frames:
...
Thank you for your help and sorry, if this is an easy question, I don't know how else to describe it. And yes, I could come up with an extra array that I set to 0 if it is further away from an error frame than 3 and use a seperate if case, but I don't find that very attractive and was wondering if there is a built in function or a nice "one-liner" ;)
Perhaps something like this:
error_frames_set = set(error_frames)
delta = 3
for x in error_sequence:
slow_frames = set(x + offset for offset in range(-delta, delta+1))
if slow_frames.intersection(error_frames_set):
cv2.imshow('Sequence', frames[x])
cv2.putText(frames[x], "Error!", (50, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 3)
cv2.waitKey(100)
else:
cv2.imshow('Sequence', frames[x])
cv2.waitKey(50)
Like so maybe:
if any(y in error_frames for y in xrange(x-3, x+4)):
...
You can consider creating a set out of your error_frames to improve the time complexity of the contains-check:
error_frame_set = set(error_frames)
And then use this set in all the checks.

PYGTK uses more screen space than expected

I am using PYGTK to program a very simple download manager, using both wGet and Python. Everything does well but it eats up a lot of screen space...
My code:
#!/usr/bin/python
import gtk
import os
def submitdownload(self):
os.system("wget "+site.get_text() + " -P "+ directory.get_text())
main=gtk.Window()
main.set_title("Simple Downloader with wGet")
structure=gtk.Table(2, 6, True)
label=gtk.Label("Simple downloader with wGet")
sitedes=gtk.Label("Your download link:")
site=gtk.Entry()
submit=gtk.Button("Submit download")
submit.connect("clicked", submitdownload)
directorydes=gtk.Label("Save to: ")
directory=gtk.Entry()
description=gtk.Label("Please don't close the black box (terminal window) or the application will close automatically. It is needed for the download.")
main.add(structure)
structure.attach(label, 0, 2, 0, 1)
structure.attach(sitedes, 0, 1, 1, 2)
structure.attach(site, 1, 2, 1, 2)
structure.attach(submit, 0, 2, 4, 5)
structure.attach(directorydes, 0, 1, 2, 3)
structure.attach(directory, 1, 2, 2, 3)
structure.attach(description, 0, 2, 5, 6)
main.connect("destroy", lambda w: gtk.main_quit())
main.show_all()
gtk.main()
It throws a lot of unused space at the right. How to fix that? It's very hard to close the application through the 'X' button.
You appear to be creating a table with 2 rows and 6 columns as opposed to the 6 rows and 2 columns I assume you're after - look at the reference documentation and you'll see rows come first in the constructor.
Because you've set homogenous to True, the table is setting all columns to the same width and height (that's what homogenous does), and because you've asked for 6 columns, it's adding a lot of blank ones of the same width which makes your window tremendously wide.
Change the line to:
structure = gtk.Table(6, 2, True)
... and it seems more reasonable. Was that what you were after?
Personally I would suggest creating a HBox to represent the column. When you need full width widgets, you can just place them directly into this container. If you need a row with multiple widgets, you can create a VBox to represent the row, add the widgets to that and then add the VBox itself to the HBox. This approach may seem slightly fiddlier at first, but it allows GTK to handle more of the layout itself which generally makes your application handle resizing better (as long as you correctly hint whether each widget should be expandable or not). Also, you don't need to go back and change the number of rows and columns if you add more widgets later - VBox and HBox are more flexible in that regard. So overall, I've always found these a lot easier unless what I'm after really is a fixed grid of widgets (e.g. if I'm implementing Minesweeper).

Images in wx.ListControl are cut off

I have a wx.ListCtrl in REPORT mode and i use an image list to display icons which are 50x50 pixels with SetItemColumnImage. The problem now is that the text I display in the column right of the icon is less than 50 pixels high and the parts of the icons that are higher than the text are cut off.
Is there a way to tell ListCtrl to adjust the row height to the height of the icons? Last refuge would be to change the fontsize of the text, but there should be a better way.
Update:
Here is some of my code:
self.list = util.ListCtrl(nb, style=wx.LC_REPORT|
wx.LC_SINGLE_SEL|wx.LC_NO_HEADER|wx.LC_ALIGN_LEFT)
self.list.InsertColumn(0, 'Avatar', width=-1)
self.list.InsertColumn(1, 'Name', width=-1)
self.list.SetColumnWidth(0, 50)
self.imagelist = wx.ImageList(50, 50, 255, 20)
self.list.SetImageList(self.imagelist, wx.IMAGE_LIST_SMALL)
i = 0
for user in self.users:
self.list.Append(['', user['name']])
if user['avatar']:
bitmap = wx.BitmapFromImage(user['avatar'])
imageidx = self.imagelist.Add(bitmap)
self.list.SetItemColumnImage(i, 0, imageidx)
i += 1
When I remove the LC_REPORT flag the images are completely visible but they are all displayed in one row and the names aren't visible anymore.
Since the images are 50x50, I don't think they qualify as "small" any more. Try using the wx.IMAGE_LIST_NORMAL instead of wx.IMAGE_LIST_SMALL. I can't find anything about manually setting row height, so I'm guessing that's not possible. However, I did find a bug report on this topic that says it was resolved in wx2.9. Are you using 2.9?
Alternatively, you could use the UltimateListCtrl which is pure Python and if it doesn't have that ability, you can probably get it patched quickly as the author is very responsive.
Took me a couple cups of coffee to figure it out.
The call to ImageList.Add should precede ListCtrl.Append (or ListCtrl.InsertItem) in order for the ListCtrl to change the height of its rows according to the height of images in ImageList.
So instead of
for user in self.users:
self.list.Append(['', user['name']])
if user['avatar']:
bitmap = wx.BitmapFromImage(user['avatar'])
imageidx = self.imagelist.Add(bitmap)
self.list.SetItemColumnImage(i, 0, imageidx)
You should go with something like this
for user in self.users:
if user['avatar']:
bitmap = wx.BitmapFromImage(user['avatar'])
imageidx = self.imagelist.Add(bitmap)
self.list.Append(['', user['name']])
if user['avatar']:
self.list.SetItemColumnImage(i, 0, imageidx)
Which looks ugly, until you implement a default avatar:
def_avatar = 'default_avatar.jpg'
for user in self.users:
bitmap = wx.BitmapFromImage(user['avatar'] if user['avatar'] else def_avatar)
imageidx = self.imagelist.Add(bitmap)
self.list.Append(['', user['name']])
self.list.SetItemColumnImage(i, 0, imageidx)

Categories