How to pass pixbuf from a class in a module,after user interaction, in Python - python

Im working on a python module to handle screenshots. The module itself works fine if alone. however when i import it into my main project i cant figure out how to return a pixbuf after waiting for the user to select a region to capture.
here is the screenshot module:
class CaptureRegion():
def __init__(self):
self.ThePic = None
self.drawArea = Gtk.DrawingArea()
self.drawArea.connect('draw',self.onDraw)
mBox = Gtk.VBox(False,2)
mBox.pack_start(self.drawArea,True,True,2)
self.MaskWin = Gtk.Window()
self.MaskWin.set_position(Gtk.WindowPosition.CENTER)
self.MaskWin.connect("key-press-event", self.KeyPress)
self.MaskWin.connect("button-press-event", self.ButtonPress)
self.MaskWin.connect("button-release-event", self.ButtonRelease)
self.MaskWin.connect("motion-notify-event", self.Motion)
self.MaskWin.add(mBox)
self.button_x = 0
self.button_y = 0
self.button_down = False
self.sel_rect = Gdk.Rectangle(-10, -10, 10, 10)
self.capfull = CaptureScreen()
self.fullpixbuf = self.capfull.Go()
def onDraw(self, widget, event):
print("Draw")
myGdkWindow = self.MaskWin.get_window()
self.cr = Gdk.cairo_create(myGdkWindow)
self.cr.set_operator(cairo.OPERATOR_SOURCE)
self.cr.set_source_rgb(1,1,1)
Gdk.cairo_set_source_pixbuf(self.cr, self.fullpixbuf, 5, 5)
self.cr.paint()
self.cr.set_line_width(1)
self.cr.set_source_rgb(0,0,0)
if self.button_down == True:
x = self.sel_rect.x
y = self.sel_rect.y
w = self.sel_rect.width
h = self.sel_rect.height
if w <= 0 or h <= 0:
return True
self.cr.rectangle(x,y,w,h)
self.cr.set_source_rgba(0.2, 0.6, 0.8, 0.35)
self.cr.set_line_width(2)
self.cr.stroke()
return True
def Motion(self,widget,event):
if self.button_down == False:
return False
x1,y1 = self.button_x, self.button_y
x2,y2 = event.x, event.y
x = int(min(x1,x2))
y = int(min(y1,y2))
w = int(abs(x1-x2))
h = int(abs(y1-y2))
old = self.sel_rect
self.sel_rect.x = x
self.sel_rect.y = y
self.sel_rect.width = w
self.sel_rect.height = h
win = self.MaskWin.get_window()
sx, sy, sw, sh = self.MaskWin.get_window().get_geometry()
self.drawArea.queue_draw_area(sx,sy,sw,sh)
def ButtonPress(self,widget,event):
if not event.button == 1:
return False
print("button down")
self.debounce = 0
self.button_x, self.button_y = event.x, event.y
self.button_down = True
return True
def ButtonRelease(self,widget,event):
self.button_down = False
x = self.sel_rect.x
y = self.sel_rect.y
w = self.sel_rect.width
h = self.sel_rect.height
self.drawArea.queue_draw_area(x-1, y-1, w-1, h-1)
if self.debounce == 0:
self.debounce += 1
print("Snipping x:%s y:%s w:%s h:%s" %(x,y,w,h))
self.ThePic = Gdk.pixbuf_get_from_window(self.MaskWin.get_window(), x, y, w, h)
self.MaskWin.hide()
#option 2: to return ThePic here after the selection is made thru a callback to a function in the main class
# i.e.
#self.CallbackInMain(self.ThePic) (not sure how to callback to the main class, self is wrong maybe?)
def KeyPress(self,widget,event):
if event.keyval == Gdk.KEY_Escape:
self.ThePic = None
return
def WaitThread(self):
for i in range(1, 50):
time.sleep(0.1)
print(i)
self.done_waiting.set()
def Go(self):
self.MaskWin.fullscreen()
self.MaskWin.show_all()
self.MaskWin.set_decorated(False)
#option 1 find a way to wait for the selection to complete like this:
#(this code doesnt end, the self.done_waiting.set() call doesnt stop it)
# self.done_waiting = threading.Event()
# self.thread = threading.Thread(target=self.WaitThread)
# self.thread.start()
# self.done_waiting.wait(1)
# print("Main thread is done waiting")
return(self.ThePic)
class CaptureScreen():
def __init__(self):
self.ThePic = None
def Go(self):
screen = Gdk.get_default_root_window()
x, y, width, height = screen.get_geometry()
self.fullpixbuf = Gdk.pixbuf_get_from_window(screen, x, y, width, height)
return(self.fullpixbuf)
im calling the class like this:
import Screentshot as Shot
self.captureregion = Shot.CaptureRegion()
pic = self.captureregion.Go()
pic.savev("region.png",'png',(),())
with the CaptureScreen class im just capturing the whole screen so i can return the pixbuf directly from the CaptureScreen.Go() function. however with the CaptureRegion class i have to wait for the user to select an area, and im not sure how to have CaptureRegion.Go() return the pixbuf after the users selection. I have two ideas, one use threading to wait for the selection to complete and then return the pixbuf from Capture Region.Go() (option 1 in code) , or second somehow have the screenshot class callback to function in main with the pixbuf as an arg(option 2 in code)
Im not very familiar with classes nor have i ever used the threading module. ive searched and found lots of info, i just cant quite wrap my head around what i need to do.

Of course now that i finally post a question about this, i figured it out. i just needed to add a second arg to Go(self,mainself) and call back to a function in the main code.
this is what i changed:
def ButtonRelease(self,widget,event):
self.button_down = False
x = self.sel_rect.x
y = self.sel_rect.y
w = self.sel_rect.width
h = self.sel_rect.height
self.drawArea.queue_draw_area(x-1, y-1, w-1, h-1)
if self.debounce == 0:
self.debounce += 1
print("Snipping x:%s y:%s w:%s h:%s" %(x,y,w,h))
self.ThePic = Gdk.pixbuf_get_from_window(self.MaskWin.get_window(), x, y, w, h)
self.MaskWin.hide()
self.GotTheShot = True
self.mainself.ScreenShotCallBack(self.ThePic)
def Go(self,main):
self.mainself = main
self.GotTheShot = False
self.MaskWin.fullscreen()
self.MaskWin.show_all()
self.MaskWin.set_decorated(False)
and here is the call and callback:
def ScreenShotRegion(self):
pic = self.captureregion.Go(self)# self,mainself... self passed by default
pic.savev("region.png",'png',(),())
def ScreenShotCallBack(self,capturepixbuf):
self.window.show_all()
capturepixbuf.savev("region.png",'png',(),())

Related

Python backtracking maze really slow

check the edit,
I wrote some code while watching The Coding Train on youtube.. the maze backtracking generator. The youtuber wrote the code in javascript and I tried to understand the code while writing in python. It seems he had to change the framerate because his program was so fast just to see the generating part. While mine after some 10ish squares it was already so slow.
It cant be an hardware problem, I've got an i5-4690K CPU and a good matching GPU, it must be something in the code! But I can't find what it is.
I rewatched the episodes so I could see what was wrong, but it seems I wrote everything just fine.
from tkinter import *
import math
import random
import time
# initialization canvas
root = Tk()
canvas = Canvas(root, width=400, height=400, bg="#333333")
canvas.pack()
# some global variables
w = 40;
cols = math.floor(int(canvas["width"])/w)
rows = math.floor(int(canvas["height"])/w)
grid = []
current = None
class Cell():
line_color = "#AAAAAA"
visited_color = "green"
visited = False
rectangle = None
def __init__(self, i, j):
self.i = i
self.j = j
self.wall = [True, True, True, True] # top , right, bottom, left
def __repr__(self):
return "({}, {})".format(self.i, self.j)
def draw_lines(self):
x = self.i*w
y = self.j*w
if self.visited :
self.rectangle = canvas.create_rectangle(x, y, x+w, y+w, fill="purple", outline="")
canvas.update()
if self.wall[0]:
canvas.create_line(x, y, x+w, y, fill=self.line_color)
else:
canvas.create_line(x, y, x+w, y, fill="purple")
if self.wall[1]:
canvas.create_line(x+w, y, x+w, y+w, fill=self.line_color)
else:
canvas.create_line(x+w, y, x+w, y+w, fill="purple")
if self.wall[2]:
canvas.create_line(x, y+w, x+w, y+w, fill=self.line_color)
else:
canvas.create_line(x, y+w, x+w, y+w, fill="purple")
if self.wall[3]:
canvas.create_line(x, y, x, y+w, fill=self.line_color)
else:
canvas.create_line(x, y, x, y+w, fill="purple")
def checkNeighbors(self):
neighbors = []
top = None
bottom = None
left = None
right = None
if index(self.i, self.j-1) != -1:
top = grid[index(self.i, self.j-1)]
if index(self.i, self.j+1) != -1:
bottom = grid[index(self.i, self.j+1)]
if index(self.i-1, self.j) != -1:
left = grid[index(self.i-1, self.j)]
if index(self.i+1, self.j) != -1:
right = grid[index(self.i+1, self.j)]
if top is not None and top.visited is False:
neighbors.append(top)
if right is not None and right.visited is False:
neighbors.append(right)
if bottom is not None and bottom.visited is False:
neighbors.append(bottom)
if left is not None and left.visited is False:
neighbors.append(left)
if len(neighbors) > 0:
r = random.randint(0, len(neighbors)-1)
return neighbors[r]
else:
return None
def removeWalls(a, b):
x = a.i - b.i
y = a.j - b.j
if x != 0:
if x == 1:
a.wall[3] = False
b.wall[1] = False
else:
a.wall[1] = False
b.wall[3] = False
if y != 0:
if y == 1:
a.wall[0] = False
b.wall[2] = False
else:
a.wall[2] = False
b.wall[0] = False
def index(i, j):
if j < 0 or j > rows - 1 or i < 0 or i > cols - 1:
return -1
return j + i * cols
def setup():
global current
for i in range(rows):
for j in range(cols):
cell = Cell(i, j)
grid.append(cell)
current = grid[0]
next_one = None
def draw():
global current
global next_one
stack = []
almost = False
while True:
current.visited = True
for cell in grid:
cell.draw_lines()
next_one = current.checkNeighbors()
if next_one:
stack.append(current)
removeWalls(current, next_one)
current = next_one
elif len(stack) > 0:
cell = stack.pop()
current = cell
for cell in grid:
print(cell.visited)
setup()
draw()
root.mainloop()
I'm sorry to put all the code, but I think all of it is relevant for what comes to performance, haven't put any useful comments, sorry I'm trying to become a better programmer and change that bad habit
BIG EDIT:
I tested to just draw my maze once I finished calculating it, and it takes less than a second, so I figure it has to be with the amount of widgets (lines) I'm creating..? How could I minimize the widgets so I could see the maze being created like I wanted to?
You draw lines even if they didn't change. Instead, draw only the changed cells.
Besides, you have endless loop. Even if stack contains no more elements, you don't stop. Here you should break the loop.
Here is an improvement:
def draw():
for cell in grid:
cell.draw_lines()
global current
global next_one
stack = []
almost = False
while True:
current.visited = True
next_one = current.checkNeighbors()
if next_one:
stack.append(current)
removeWalls(current, next_one)
current.draw_lines()
next_one.draw_lines()
current = next_one
elif len(stack) > 0:
cell = stack.pop()
current = cell
else:
break

Grid Generation for minesweeper

Hi so I am making a minesweeper game and I am a bit stuck with the grid generation part. This is my code so far:
from random import randint
import pygame
def MineGen():
mineamount = 100
grid_across = 40
grid_up = 25
mine_list = []
my2dthatlist = []
numacoss = 0
for i in range (mineamount):
numacoss = randint(1,40)
my2dthatlist.append(numacoss)
numup = randint(1,25)
my2dthatlist.append(numup)
mine_list.append(my2dthatlist)
my2dthatlist = []
return mine_list
def GridGen():
grid_across = 40
grid_up = 25
GRIDD = [[0]* grid_across for i in range(grid_up)]
return GRIDD
def MineGrid(GridOutMine, mine_list):
mineplace = 0
placeX = 0
placeY = 0
for i in range(100):
mineplace = mine_list[i]
placeX = mineplace[0]
placeY = mineplace[1]
GridOutMine[placeX][placeY] = 1
print(GridOutMine)
mine_list = MineGen()
GridOutMine = GridGen()
MineGrid(GridOutMine, mine_list)
My issue is that i am getting a list index out of range for the
GridOutMine[placeX][placeY] = 1
part. I don't really know why this is. If you could give me some assistance in what to do, or just some general comments on my code, I would really appreciate it thanks.
That's because, unlike range, random.randint outputs numbers within the specified bounds inclusively. That is, randint(1, 25) could output 25, which is not a valid index for a list that's only 25 elements long (since the last index is 24).
In MineGen, you need to change randint(1, 25) to randint(1, 25-1) or randint(1, 24), and likewise for randint(1, 40) which needs to be randint(1, 39). I'd actually suggest randint(0, 24) and (0, 39), but I don't know if that's intentional.
There are many other things that could/should be improved about this code, but I'd suggest you ask for that kind of input over on CodeReview instead of here once your code is working (they don't fix broken code).
EDIT:
Also, you're indexing your grid in the wrong order. It's a list (25-long) of rows (40-long), so you need to index it in the Y dimension first, then X: GridOutMine[placeY][placeX] = 1
If you are trying to build a grid for the game and want to display buttons in a GUI your users can interact with, you may want to start with the following code as a basic framework for the rest of the code you will be writing.
import tkinter
import functools
class MineSweep(tkinter.Frame):
#classmethod
def main(cls, width, height):
root = tkinter.Tk()
window = cls(root, width, height)
root.mainloop()
def __init__(self, master, width, height):
super().__init__(master)
self.__width = width
self.__height = height
self.__build_buttons()
self.grid()
def __build_buttons(self):
self.__buttons = []
for y in range(self.__height):
row = []
for x in range(self.__width):
button = tkinter.Button(self)
button.grid(column=x, row=y)
button['text'] = '?'
command = functools.partial(self.__push, x, y)
button['command'] = command
row.append(button)
self.__buttons.append(row)
def __push(self, x, y):
print('Column = {}\nRow = {}'.format(x, y))
if __name__ == '__main__':
MineSweep.main(10, 10)
If you want a more complete example of a minesweeper game to either borrow ideas from or adapt for your own needs, the following program implements much of the functionality you might want from a finished game.
import tkinter
import functools
import random
from tkinter.simpledialog import askstring, Dialog
from tkinter.messagebox import showinfo
import os.path
################################################################################
class MineSweep(tkinter.Frame):
#classmethod
def main(cls, width, height, mines, scores):
root = tkinter.Tk()
root.resizable(False, False)
root.title('MineSweep')
window = cls(root, width, height, mines, scores)
root.protocol('WM_DELETE_WINDOW', window.close)
root.mainloop()
################################################################################
def __init__(self, master, width, height, mines, scores):
super().__init__(master)
self.__width = width
self.__height = height
self.__mines = mines
self.__wondering = width * height
self.__started = False
self.__playing = True
self.__scores = ScoreTable()
self.__record_file = scores
if os.path.isfile(scores):
self.__scores.load(scores)
self.__build_timer()
self.__build_buttons()
self.grid()
def close(self):
self.__scores.save(self.__record_file)
self.quit()
def __build_timer(self):
self.__secs = tkinter.IntVar()
self.__timer = tkinter.Label(textvariable=self.__secs)
self.__timer.grid(columnspan=self.__width, sticky=tkinter.EW)
self.__after_handle = None
def __build_buttons(self):
self.__reset_button = tkinter.Button(self)
self.__reset_button['text'] = 'Reset'
self.__reset_button['command'] = self.__reset
self.__reset_button.grid(column=0, row=1,
columnspan=self.__width, sticky=tkinter.EW)
self.__reset_button.blink_handle = None
self.__buttons = []
for y in range(self.__height):
row = []
for x in range(self.__width):
button = tkinter.Button(self, width=2, height=1,
text='?', fg='red')
button.grid(column=x, row=y+2)
command = functools.partial(self.__push, x, y)
button['command'] = command
row.append(button)
self.__buttons.append(row)
def __reset(self):
for row in self.__buttons:
for button in row:
button.config(text='?', fg='red')
self.__started = False
self.__playing = True
self.__wondering = self.__width * self.__height
if self.__after_handle is not None:
self.after_cancel(self.__after_handle)
self.__after_handle = None
self.__secs.set(0)
def __push(self, x, y, real=True):
button = self.__buttons[y][x]
if self.__playing:
if not self.__started:
self.__build_mines()
while self.__buttons[y][x].mine:
self.__build_mines()
self.__started = True
self.__after_handle = self.after(1000, self.__tick)
if not button.pushed:
self.__push_button(button, x, y)
elif real:
self.__blink(button, button['bg'], 'red')
elif real:
self.__blink(button, button['bg'], 'red')
def __blink(self, button, from_bg, to_bg, times=8):
if button.blink_handle is not None and times == 8:
return
button['bg'] = (to_bg, from_bg)[times & 1]
times -= 1
if times:
blinker = functools.partial(self.__blink, button,
from_bg, to_bg, times)
button.blink_handle = self.after(250, blinker)
else:
button.blink_handle = None
def __tick(self):
self.__after_handle = self.after(1000, self.__tick)
self.__secs.set(self.__secs.get() + 1)
def __push_button(self, button, x, y):
button.pushed = True
if button.mine:
button['text'] = 'X'
self.__playing = False
self.after_cancel(self.__after_handle)
self.__after_handle = None
self.__blink(self.__reset_button, button['bg'], 'red')
else:
button['fg'] = 'SystemButtonText'
count = self.__total(x, y)
button['text'] = count and str(count) or ' '
self.__wondering -= 1
if self.__wondering == self.__mines:
self.after_cancel(self.__after_handle)
self.__after_handle = None
self.__finish_game()
def __finish_game(self):
self.__playing = False
score = self.__secs.get()
for row in self.__buttons:
for button in row:
if button.mine:
button['text'] = 'X'
if self.__scores.eligible(score):
name = askstring('New Record', 'What is your name?')
if name is None:
name = 'Anonymous'
self.__scores.add(name, score)
else:
showinfo('You did not get on the high score table.')
HighScoreView(self, 'High Scores', self.__scores.listing())
def __total(self, x, y):
count = 0
for x_offset in range(-1, 2):
x_index = x + x_offset
for y_offset in range(-1, 2):
y_index = y + y_offset
if 0 <= x_index < self.__width and 0 <= y_index < self.__height:
count += self.__buttons[y_index][x_index].mine
if not count:
self.__propagate(x, y)
return count
def __propagate(self, x, y):
for x_offset in range(-1, 2):
x_index = x + x_offset
for y_offset in range(-1, 2):
y_index = y + y_offset
if 0 <= x_index < self.__width and 0 <= y_index < self.__height:
self.__push(x_index, y_index, False)
def __build_mines(self):
mines = [True] * self.__mines
empty = [False] * (self.__width * self.__height - self.__mines)
total = mines + empty
random.shuffle(total)
iterator = iter(total)
for row in self.__buttons:
for button in row:
button.mine = next(iterator)
button.pushed = False
button.blink_handle = None
################################################################################
class ScoreTable:
def __init__(self, size=10):
self.__data = {999: [''] * size}
def add(self, name, score):
assert self.eligible(score)
if score in self.__data:
self.__data[score].insert(0, name)
else:
self.__data[score] = [name]
if len(self.__data[max(self.__data)]) == 1:
del self.__data[max(self.__data)]
else:
del self.__data[max(self.__data)][-1]
def eligible(self, score):
return score <= max(self.__data)
def listing(self):
for key in sorted(self.__data.keys()):
for name in self.__data[key]:
yield name, key
def load(self, filename):
self.__data = eval(open(filename, 'r').read())
def save(self, filename):
open(filename, 'w').write(repr(self.__data))
################################################################################
class HighScoreView(Dialog):
def __init__(self, parent, title, generator):
self.__scores = generator
super().__init__(parent, title)
def body(self, master):
self.__labels = []
for row, (name, score) in enumerate(self.__scores):
label = tkinter.Label(master, text=name)
self.__labels.append(label)
label.grid(row=row, column=0)
label = tkinter.Label(master, text=str(score))
self.__labels.append(label)
label.grid(row=row, column=1)
self.__okay = tkinter.Button(master, command=self.ok, text='Okay')
self.__okay.grid(ipadx=100, columnspan=2, column=0, row=row+1)
return self.__okay
def buttonbox(self):
pass
################################################################################
if __name__ == '__main__':
MineSweep.main(10, 10, 10, 'scores.txt')
Reference: ActiveState Code » Recipes » MineSweep

Any way to make a Python GTK window quit automatically?

I'm writing a code that I would like to repeat automatically, and for the most part I have it. If I use a while loop I can get it to repeat, but I have to close the window automatically for it to work. Is there any way I can get it to close by itself?
The program is a slideshow.
Code wise, the loop I'm using looks like this:
while True:
execfile("slideshowtest.py")
If you need the rest of the code it looks like this:
import os
import pygtk
pygtk.require('2.0')
import gtk
import glib
def is_image(filename):
if not os.path.isfile(filename):
return False
for suffix in ['.jpg', '.png', '.bmp']:
if filename.lower().endswith(suffix):
return True
return False
def resizeToFit(image, frame, aspect=True, enlarge=False):
if aspect:
return scaleToFit(image, frame, enlarge)
else:
return stretchToFit(image, frame, enlarge)
def scaleToFit(image, frame, enlarge=False):
image_width, image_height = image
frame_width, frame_height = frame
image_aspect = float(image_width) / image_height
frame_aspect = float(frame_width) / frame_height
if not enlarge:
max_width = min(frame_width, image_width)
max_height = min(frame_height, image_height)
else:
max_width = frame_width
max_height = frame_height
if frame_aspect > image_aspect:
height = max_height
width = int(height * image_aspect)
else:
width = max_width
height = int(width / image_aspect)
return (width, height)
def stretchToFit(image, frame, enlarge=False):
image_width, image_height = image
frame_width, frame_height = frame
if not enlarge:
width = min(frame_width, image_width)
height = min(frame_height, image_height)
else:
width = frame_width
height = frame_height
return (width, height)
class ResizableImage(gtk.DrawingArea):
def __init__(self, aspect=True, enlarge=False,
interp=gtk.gdk.INTERP_NEAREST, backcolor=None, max=(1600,1200)):
super(ResizableImage, self).__init__()
self.pixbuf = None
self.aspect = aspect
self.enlarge = enlarge
self.interp = interp
self.backcolor = backcolor
self.max = max
self.connect('expose_event', self.expose)
self.connect('realize', self.on_realize)
def on_realize(self, widget):
if self.backcolor is None:
color = gtk.gdk.Color()
else:
color = gtk.gdk.Color(*self.backcolor)
self.window.set_background(color)
def expose(self, widget, event):
self.context = self.window.cairo_create()
self.context.rectangle(
event.area.x, event.area.y,
event.area.width, event.area.height)
self.context.clip()
self.draw(self.context)
return False
def draw(self, context):
rect = self.get_allocation()
x, y = rect.x, rect.y
parent = self.get_parent()
if parent:
offset = parent.get_allocation()
x -= offset.x
y -= offset.y
if self.backcolor:
context.rectangle(x, y, rect.width, rect.height)
context.set_source_rgb(*self.backcolor)
context.fill_preserve()
if not self.pixbuf:
return
width, height = resizeToFit(
(self.pixbuf.get_width(), self.pixbuf.get_height()),
(rect.width, rect.height),
self.aspect,
self.enlarge)
x = x + (rect.width - width) / 2
y = y + (rect.height - height) / 2
context.set_source_pixbuf(
self.pixbuf.scale_simple(width, height, self.interp), x, y)
context.paint()
def set_from_pixbuf(self, pixbuf):
width, height = pixbuf.get_width(), pixbuf.get_height()
if not self.max or (width < self.max[0] and height < self.max[1]):
self.pixbuf = pixbuf
else:
width, height = resizeToFit((width, height), self.max)
self.pixbuf = pixbuf.scale_simple(
width, height,
gtk.gdk.INTERP_BILINEAR)
self.invalidate()
def set_from_file(self, filename):
self.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file(filename))
def invalidate(self):
self.queue_draw()
class DemoGtk:
SECONDS_BETWEEN_PICTURES = 2
FULLSCREEN = True
WALK_INSTEAD_LISTDIR = True
def __init__(self):
self.window = gtk.Window()
self.window.connect('destroy', gtk.main_quit)
self.window.set_title('Slideshow')
self.image = ResizableImage( True, True, gtk.gdk.INTERP_BILINEAR)
self.image.show()
self.window.add(self.image)
self.load_file_list()
self.window.show_all()
if self.FULLSCREEN:
self.window.fullscreen()
glib.timeout_add_seconds(self.SECONDS_BETWEEN_PICTURES, self.on_tick)
self.display()
def load_file_list(self):
self.files = []
self.index = 0
if self.WALK_INSTEAD_LISTDIR:
for directory, sub_directories, files in os.walk('.'):
for filename in files:
if is_image(filename):
filepath = os.path.join(directory, filename)
self.files.append(filepath)
else:
for filename in os.listdir('.'):
if is_image(filename):
self.files.append(filename)
print "Images:", self.files
def display(self):
if 0 <= self.index < len(self.files):
self.image.set_from_file(self.files[self.index])
return True
else:
return False
def on_tick(self):
self.index += 1
return self.display()
if __name__ == "__main__":
gui = DemoGtk()
gtk.main()
while True:
execfile("slideshowtest.py")
gtk Functions:
The gtk.main() function runs the main loop until the gtk.main_quit() function is called.
So you need to run your while loop in a separate thread and call main_quit() when you're finished.
Extending Ivan's answer, I needed window to close automatically after a couple of seconds. I ended up with something similar.
import threading
import time
def thread_function(name):
time.sleep(DISPLAY_TIME)
Gtk.main_quit()
x = threading.Thread(target=thread_function, args=(1,))
x.start() # Start thread and let thread sleep for N seconds
Gtk.main() # Start main Gtk
x.join()

How to stop other processes or finish processes in Python / Tkinter program

I have been drawing some graphics with a Python / Tkinter program. The program has a main menu with menu items for drawing different figures. It works quite well but I came up against a problem. If the program is part way through drawing one figure and the user clicks to draw a second figure then the program draws the second figure, but when it has finished drawing the second figure it goes back and finishes drawing the first figure. What I want it to do is stop drawing the first figure and not go back to drawing the first figure even when the second figure has finished drawing. I created an simpler example program to demonstrate the scenario. To see the problem in this program click "Draw -> Red" and then click "Draw -> Blue" before the red has finished drawing. How do I get the program to abort any previous drawing? Here is the example program:
from tkinter import *
import random
import math
def line(canvas, w, h, p, i):
x0 = random.randrange(0, w)
y0 = random.randrange(0, h)
x1 = random.randrange(0, w)
y1 = random.randrange(0, h)
canvas.create_line(x0, y0, x1, y1, fill=p.col(i))
class Color:
def __init__(self, r, g, b):
self.red = r
self.gre = g
self.blu = b
def hexVal(self, v):
return (hex(v)[2:]).zfill(2)
def str(self):
return "#" + self.hexVal(self.red) + self.hexVal(self.gre) + self.hexVal(self.blu)
class Palette:
def __init__(self, n0, y):
self.colors = []
self.n = n0
self.m = 0
if y == "red":
self.red()
elif y == "blue":
self.blue()
def add(self, c):
self.colors.append(c)
self.m += 1
def red(self):
self.add(Color(127, 0, 0))
self.add(Color(255, 127, 0))
def blue(self):
self.add(Color(0, 0, 127))
self.add(Color(0, 127, 255))
def col(self, i):
k = i % (self.n*self.m)
z = k // self.n
j = k % self.n
c0 = self.colors[z]
c1 = self.colors[(z + 1) % self.m]
t0 = (self.n - j)/self.n
t1 = j/self.n
r = int(math.floor(c0.red*t0 + c1.red*t1))
g = int(math.floor(c0.gre*t0 + c1.gre*t1))
b = int(math.floor(c0.blu*t0 + c1.blu*t1))
c = Color(r, g, b)
return c.str()
def upd(canvas):
try:
canvas.update()
return True
except TclError:
return False
def tryLine(canvas, w, h, p, i, d):
try:
line(canvas, w, h, p, i)
if i % d == 0:
upd(canvas)
return True
except TclError:
return False
class MenuFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.WIDTH = 800
self.HEIGHT = 800
self.canvas = Canvas(self.parent, width=self.WIDTH, height=self.HEIGHT)
self.pack(side=BOTTOM)
self.canvas.pack(side=TOP, fill=BOTH, expand=1)
self.parent.title("Line Test")
menubar = Menu(self.parent)
self.parent.config(menu=menubar)
self.parent.protocol('WM_DELETE_WINDOW', self.onExit)
menu = Menu(menubar)
menu.add_command(label="Red", command=self.onRed)
menu.add_command(label="Blue", command=self.onBlue)
menu.add_command(label="Exit", command=self.onExit)
menubar.add_cascade(label="Draw", menu=menu)
self.pRed = Palette(256, "red")
self.pBlue = Palette(256, "blue")
def onRed(self):
# How to abort here any processes currently running?
self.canvas.delete("all")
for i in range(0, 7000):
tryLine(self.canvas, self.WIDTH, self.HEIGHT, self.pRed, i, 100)
upd(self.canvas)
def onBlue(self):
# How to abort here any processes currently running?
self.canvas.delete("all")
for i in range(0, 7000):
tryLine(self.canvas, self.WIDTH, self.HEIGHT, self.pBlue, i, 100)
upd(self.canvas)
def onExit(self):
self.canvas.delete("all")
self.parent.destroy()
def main():
root = Tk()
frame = MenuFrame(root)
root.mainloop()
if __name__ == '__main__':
main()
That's the simple example? ;)
You can add a variable that tracks the currently selected method, then check if that variable exists before completing the for loop. Here's an even simpler example:
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
Button(self, text='Task A', command=self._a).pack()
Button(self, text='Task B', command=self._b).pack()
self.current_task = None # this var will hold the current task (red, blue, etc)
def _a(self):
self.current_task = 'a' # set the current task
for i in range(1000):
if self.current_task == 'a': # only continue this loop if its the current task
print('a')
self.update()
def _b(self):
self.current_task = 'b'
for i in range(1000):
if self.current_task == 'b':
print('b')
self.update()
root = Tk()
Example(root).pack()
root.mainloop()
Let me know if that doesn't make sense or doesn't work out for you.

how to repaint only part of a QWidget in PyQt4?

I'm trying to create a program that displays a large grid of numbers (say, filling up a 6 by 4000 grid), where the user can move a cursor around via keyboard or mouse and enter in numbers into the grid. (This is for a guitar tablature program.) I'm new to python GUI programming, and thus far my idea is to have a very large QWidget window (say, 1000x80000 pixels) inside of a QScrollArea inside of the main window. The problem is that every mouse click or cursor movement causes the whole thing to repaint, causing a delay, when I just want to repaint whatever changes I just made to make things faster. In PyQt, is there a way to buffer already-painted graphics and change just the graphics that need changing?
edit: I've posted the code below, which I've run with python3.3 on Mac OS 10.7. The main point is that in the TabWindow init function, the grid size can be set by numXGrid and numYGrid (currently set to 200 and 6), and this grid is filled with random numbers by the generateRandomTablatureData() method. If the grid is filled with numbers, then there's a noticeable lag with every key press, which gets worse with larger grids. (There is also an initial delay due to generating the data, but my question is on the delay after each key press which I assume is due to having to repaint every number.)
There are two files. This is the main one, which I called FAIT.py:
import time
start_time = time.time()
import random
import sys
from PyQt4 import QtGui, QtCore
import Tracks
# generate tracks
tracks = [Tracks.Track(), Tracks.Track(), Tracks.Track()]
fontSize = 16
# margins
xMar = 50
yMar = 50
trackMar = 50 # margin between tracks
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
end_time = time.time()
print("Initializing time was %g seconds" % (end_time - start_time))
def initUI(self):
# attach QScrollArea to MainWindow
l = QtGui.QVBoxLayout(self)
l.setContentsMargins(0,0,0,0)
l.setSpacing(0)
s=QtGui.QScrollArea()
l.addWidget(s)
# attach TabWindow to QScrollArea so we can paint on it
self.tabWindow=TabWindow(self)
self.tabWindow.setFocusPolicy(QtCore.Qt.StrongFocus)
self.setFocusPolicy(QtCore.Qt.NoFocus)
vbox=QtGui.QVBoxLayout(self.tabWindow)
s.setWidget(self.tabWindow)
self.positionWindow() # set size and position of main window
self.setWindowTitle('MainWindow')
self.show()
def positionWindow(self):
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
width = QtGui.QDesktopWidget().availableGeometry().width() - 100
height = QtGui.QDesktopWidget().availableGeometry().height() - 100
self.resize(width, height)
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def keyPressEvent(self, e):
print('key pressed in MainWindow')
def mousePressEvent(self, e):
print('mouse click in MainWindow')
class TabWindow(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# size of tablature grid
numXGrid = 200
numYGrid = 6
# initialize tablature information first
for i in range(0, len(tracks)):
tracks[i].numXGrid = numXGrid
self.arrangeTracks() # figure out offsets for each track
self.trackFocusNum = 0 # to begin with, focus is on track 0
self.windowSizeX = tracks[0].x0 + tracks[0].dx*(tracks[0].numXGrid+2)
self.windowSizeY = tracks[0].y0
for i in range(0, len(tracks)):
self.windowSizeY = self.windowSizeY + tracks[i].dy * tracks[i].numYGrid + trackMar
self.resize(self.windowSizeX,self.windowSizeY) # size of actual tablature area
# generate random tablature data for testing
self.generateRandomTablatureData()
def keyPressEvent(self, e):
print('key pressed in TabWindow')
i = self.trackFocusNum
if e.key() == QtCore.Qt.Key_Up:
tracks[i].moveCursorUp()
if e.key() == QtCore.Qt.Key_Down:
tracks[i].moveCursorDown()
if e.key() == QtCore.Qt.Key_Left:
tracks[i].moveCursorLeft()
if e.key() == QtCore.Qt.Key_Right:
tracks[i].moveCursorRight()
# check for number input
numberKeys = (QtCore.Qt.Key_0,
QtCore.Qt.Key_1,
QtCore.Qt.Key_2,
QtCore.Qt.Key_3,
QtCore.Qt.Key_4,
QtCore.Qt.Key_5,
QtCore.Qt.Key_6,
QtCore.Qt.Key_7,
QtCore.Qt.Key_8,
QtCore.Qt.Key_9)
if e.key() in numberKeys:
num = int(e.key())-48
# add data
tracks[i].data.addToTab(tracks[i].iCursor, tracks[i].jCursor, num)
# convert entered number to pitch and play note (do later)
# spacebar, backspace, or delete remove data
if e.key() in (QtCore.Qt.Key_Space, QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete):
tracks[i].data.removeFromTab(tracks[i].iCursor, tracks[i].jCursor)
self.update()
def mousePressEvent(self, e):
print('mouse click in TabWindow')
xPos = e.x()
yPos = e.y()
# check tracks one by one
for i in range(0, len(tracks)):
if (tracks[i].isPositionInside(xPos, yPos)):
tracks[i].moveCursorToPosition(xPos, yPos)
self.trackFocusNum = i
break
else:
continue
self.update()
def paintEvent(self, e):
qp = QtGui.QPainter()
qp.begin(self)
qp.setPen(QtCore.Qt.black)
qp.setBrush(QtCore.Qt.white)
qp.drawRect(0, 0, self.windowSizeX, self.windowSizeY)
self.paintTracks(qp)
self.paintTunings(qp)
self.paintCursor(qp)
self.paintNumbers(qp)
qp.end()
def paintTracks(self, qp):
qp.setPen(QtCore.Qt.black)
qp.setBrush(QtCore.Qt.white)
for i in range(0, len(tracks)):
qp.drawPolyline(tracks[i].polyline)
def paintCursor(self, qp):
i = self.trackFocusNum
qp.setPen(QtCore.Qt.black)
qp.setBrush(QtCore.Qt.black)
qp.drawPolygon(tracks[i].getCursorQPolygon())
def paintNumbers(self, qp):
# iterate through tracks, and iterate through numbers on each track
for i in range(0, len(tracks)):
# make sure track has data to draw
if len(tracks[i].data.data) > 0:
for j in range(0, len(tracks[i].data.data)):
# do actual painting here
# first set color to be inverse cursor color if at cursor
if i == self.trackFocusNum and \
tracks[i].iCursor == tracks[i].data.data[j][0] and \
tracks[i].jCursor == tracks[i].data.data[j][1]:
qp.setPen(QtCore.Qt.white)
else:
qp.setPen(QtCore.Qt.black)
font = QtGui.QFont('Helvetica', fontSize)
qp.setFont(font)
text = str(tracks[i].data.data[j][2])
x1 = tracks[i].convertIndexToPositionX(tracks[i].data.data[j][0])
y1 = tracks[i].convertIndexToPositionY(tracks[i].data.data[j][1])
dx = tracks[i].dx
dy = tracks[i].dy
# height and width of number character(s)
metrics = QtGui.QFontMetrics(font)
tx = metrics.width(text)
ty = metrics.height()
# formula for alignment:
# xMar = (dx-tx)/2 plus offset
x11 = x1 + (dx-tx)/2
y11 = y1 + dy/2+ty/2
qp.drawText(x11, y11, text)
def paintTunings(self, qp):
qp.setPen(QtCore.Qt.black)
font = QtGui.QFont('Helvetica', fontSize)
qp.setFont(font)
for i in range(0, len(tracks)):
for j in range(0, tracks[i].numStrings):
text = tracks[i].convertPitchToLetter(tracks[i].stringTuning[j])
# height and width of characters
metrics = QtGui.QFontMetrics(font)
tx = metrics.width(text)
ty = metrics.height()
x11 = tracks[i].x0 - tx - 10
y11 = tracks[i].convertIndexToPositionY(j) + (tracks[i].dy+ty)/2
qp.drawText(x11, y11, text)
def arrangeTracks(self):
tracks[0].x0 = xMar
tracks[0].y0 = yMar
tracks[0].generateGridQPolyline()
for i in range(1, len(tracks)):
tracks[i].x0 = xMar
tracks[i].y0 = tracks[i-1].y0 + tracks[i-1].height + trackMar
tracks[i].generateGridQPolyline()
def generateRandomTablatureData(self):
t1 = time.time()
for i in range(0, len(tracks)):
# worst case scenario: fill every number
for jx in range(0, tracks[i].numXGrid):
for jy in range(0, tracks[i].numYGrid):
val = random.randint(0,9)
tracks[i].data.addToTab(jx, jy, val)
t2 = time.time()
print("Random number generating time was %g seconds" % (t2 - t1))
def main():
app = QtGui.QApplication(sys.argv)
ex = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
This is the other file, Tracks.py, which contains the Track class and supplementary methods:
# contains classes and methods relating to individual tracks
import math
from PyQt4 import QtGui, QtCore
# class for containing information about a track
class Track:
def __init__(self):
self.data = TabulatureData()
# position offset
self.x0 = 0
self.y0 = 0
self.dx = 20 # default rectangle width
self.dy = 40 # default rectangle height
# current cursor index coordinates
self.iCursor = 0
self.jCursor = 0
# default size of grid
self.numXGrid = 4000
self.numYGrid = 6
self.numStrings = self.numYGrid
# calculated maximum width and height in pixels
self.maxWidth = self.dx * self.numXGrid
self.maxHeight = self.dy * self.numYGrid
# generate points of grid and cursor
self.generateGridQPolyline()
# tuning
self.setTuning([40, 45, 50, 55, 59, 64])
# calculate bounds
self.height = self.numYGrid*self.dy
self.width = self.numXGrid*self.dx
def getCursorIndexX(self, xPos):
iPos = int(math.floor( (xPos-self.x0)/self.dx ))
return iPos
def getCursorIndexY(self, yPos):
jPos = int(math.floor( (yPos-self.y0)/self.dy ))
return jPos
def convertIndexToCoordinates(self, iPos, jPos):
return [self.ConvertIndexToPositionX(iPos),
self.ConvertIndexToPositionY(jPos)]
def convertIndexToPositionX(self, iPos):
return self.x0 + iPos*self.dx
def convertIndexToPositionY(self, jPos):
return self.y0 + jPos*self.dy
def getCursorQPolygon(self):
x1 = self.convertIndexToPositionX(self.iCursor)
y1 = self.convertIndexToPositionY(self.jCursor)
x2 = self.convertIndexToPositionX(self.iCursor+1)
y2 = self.convertIndexToPositionY(self.jCursor+1)
return QtGui.QPolygonF([QtCore.QPoint(x1, y1),
QtCore.QPoint(x1, y2),
QtCore.QPoint(x2, y2),
QtCore.QPoint(x2, y1)])
def generateGridQPolyline(self):
self.points = []
self.polyline = QtGui.QPolygonF()
for i in range(0, self.numXGrid):
for j in range(0, self.numYGrid):
x1 = self.convertIndexToPositionX(i)
y1 = self.convertIndexToPositionY(j)
x2 = self.convertIndexToPositionX(i+1)
y2 = self.convertIndexToPositionY(j+1)
self.points.append([(x1, y1), (x1, y2), (x2, y2), (x2, y1)])
self.polyline << QtCore.QPoint(x1,y1) << \
QtCore.QPoint(x1,y2) << \
QtCore.QPoint(x2,y2) << \
QtCore.QPoint(x2,y1) << \
QtCore.QPoint(x1,y1)
# smoothly connect different rows
jLast = self.numYGrid-1
x1 = self.convertIndexToPositionX(i)
y1 = self.convertIndexToPositionY(jLast)
x2 = self.convertIndexToPositionX(i+1)
y2 = self.convertIndexToPositionY(jLast+1)
self.polyline << QtCore.QPoint(x2,y1)
def isPositionInside(self, xPos, yPos):
if (xPos >= self.x0 and xPos <= self.x0 + self.width and
yPos >= self.y0 and yPos <= self.y0 + self.height):
return True
else:
return False
def moveCursorToPosition(self, xPos, yPos):
self.iCursor = self.getCursorIndexX(xPos)
self.jCursor = self.getCursorIndexY(yPos)
self.moveCursorToIndex(self.iCursor, self.jCursor)
def moveCursorToIndex(self, iPos, jPos):
# check if bounds are breached, and if cursor's already there,
# and if not, move cursor
if iPos == self.iCursor and jPos == self.jCursor:
return
if iPos >= 0 and iPos < self.numXGrid:
if jPos >= 0 and jPos < self.numYGrid:
self.iCursor = iPos
self.jCursor = jPos
return
def moveCursorUp(self):
self.moveCursorToIndex(self.iCursor, self.jCursor-1)
def moveCursorDown(self):
self.moveCursorToIndex(self.iCursor, self.jCursor+1)
def moveCursorLeft(self):
self.moveCursorToIndex(self.iCursor-1, self.jCursor)
def moveCursorRight(self):
self.moveCursorToIndex(self.iCursor+1, self.jCursor)
# return pitch in midi integer notation
def convertNumberToPitch(self, jPos, pitchNum):
return pitchNum + self.stringTuning[(self.numStrings-1) - jPos]
def convertPitchToLetter(self, pitchNum):
p = pitchNum % 12
letters = ('C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B')
return letters[p]
def setTuning(self, tuning):
self.stringTuning = tuning
class TabulatureData:
def __init__(self):
self.data = []
def addToTab(self, i, j, value):
# check if data is already there, and remove it first
if self.getValue(i, j) > -1:
self.removeFromTab(i, j)
self.data.append([i, j, value])
def getValue(self, i, j):
possibleTuples = [x for x in self.data if x[0] == i and x[1] == j]
if possibleTuples == []:
return -1
elif len(possibleTuples) > 1:
print('Warning: more than one number at a location!')
return possibleTuples[0][2] # return third number of tuple
def removeFromTab(self, i, j):
# first get value, if it exists
value = self.getValue(i,j)
if value == -1:
return
else:
# if it exists, then remove
self.data.remove([i, j, value])
1000*80000 is really huge.
So,maybe you should try QGLWidget or something like that?
Or according to Qt document, you should set which region you want to repaint.
some slow widgets need to optimize by painting only the requested region: QPaintEvent::region(). This speed optimization does not change the result, as painting is clipped to that region during event processing. QListView and QTableView do this, for example.

Categories