# Copyright (C) 2005 David Baird # Pre-requisites: # Debian: # See README.mywidgets # Fedora Core 3: # See README.mywidgets # Gentoo: # See README.mywidgets import Tkinter import threading import time import patterns import helper from PIL import ImageTk global_debug = False #image_types = [eval('PIL.%sImagePlugin.%sImageFile' % (x, x) for x in [ _ = [[ 'Arg', 'Bmp', 'Cur', 'Dcx', 'Eps', 'Fli', 'Fpx', 'Gbr', 'Gif', 'Ico', 'Im', 'Imt', 'Iptc', 'Jpeg', 'McIdas', 'Mic', 'Mpeg', 'Msp', 'Palm', 'Pcd', 'Pcx', 'Pdf', 'Pixar', 'Png', 'Ppm', 'Psd', 'Sgi', 'Sun', 'Tga', 'Tiff', 'Sun', 'Sun', ]] # FIXME: abstract this (let the user override mappings) def transform_contents(factory, content): if content is None: content = [ factory.space(ems=0) ] elif type(content) is tuple: content = list(content) elif type(content) is not list: content = [ content ] for i in xrange(len(content)): x = content[i] if type(x) == str: y = [] for token in helper.split2('\n', x): if token == '\n': y.append(factory.newline()) else: y.append(factory.label(text=token)) content[i] = y # annoying heuristic to detect PIL image types... elif (hasattr(x, 'histogram') and hasattr(x, 'tobitmap') and hasattr(x, 'thumbnail')): content[i] = factory.picture(picture=content[i]) # TO DO: consider transforming lists into tables... return helper.flatten_list(content) class Widget(patterns.Subject): def __init__(self, factory, **kwargs): patterns.Subject.__init__(self) self.factory = factory for name in kwargs: setattr(self, name, kwargs[name]) class Window(Widget): def __init__(self, factory, **kwargs): Widget.__init__(self, factory, **kwargs) title = helper.attr_accessor_indirection('title') contents = helper.attr_accessor_indirection('contents') def set_title(self, text): raise NotImplementedError def get_title(self): return self._title def set_contents(self, contents): raise NotImplementedError def get_contents(self): raise NotImplementedError class Frame(Widget): def __init__(self, factory, **kwargs): Widget.__init__(self, factory, **kwargs) # FIXME: inappropriate feature @ title? title = helper.attr_accessor_indirection('label') contents = helper.attr_accessor_indirection('contents') def set_title(self, text): raise NotImplementedError def get_title(self): return self._title def set_contents(self, widgets): raise NotImplementedError def get_contents(self): return self._contents class Button(Widget): def __init__(self, factory, **kwargs): Widget.__init__(self, factory, **kwargs) text = helper.attr_accessor_indirection('text') def set_text(self, text): raise NotImplementedError def get_text(self): return self._text class Label(Widget): def __init__(self, factory, **kwargs): Widget.__init__(self, factory, **kwargs) text = helper.attr_accessor_indirection('text') def set_text(self, text): raise NotImplementedError def get_text(self): return self._text class Edit(Widget): def __init__(self, factory, **kwargs): Widget.__init__(self, factory, **kwargs) text = helper.attr_accessor_indirection('text') def set_text(self, text): raise NotImplementedError def get_text(self): return self._text class NewLine(Widget): def __init__(self, factory, **kwargs): Widget.__init__(self, factory, **kwargs) class Space(Widget): def __init__(self, factory, **kwargs): Widget.__init__(self, factory, **kwargs) ems = helper.attr_accessor_indirection('ems') def set_ems(self, ems): raise NotImplementedError def get_ems(self, ems): return self._ems class Table(Widget): def __init__(self, factory, **kwargs): Widget.__init__(self, factory, **kwargs) rows = helper.attr_accessor_indirection('rows') columns = helper.attr_accessor_indirection('columns') contents = helper.attr_accessor_indirection('contents') def set_rows(self, rows): raise NotImplementedError def get_rows(self): return self._rows def set_columns(self, columns): raise NotImplementedError def get_columns(self): return self._columns def set_contents_at(self, contents, row1, column1, row2, column2): raise NotImplementedError def get_contents_at(self, row1, column1, row2, column2): raise NotImplementedError def set_contents(self, contents): rowi = 0 for row in contents: columni = 0 for item in row: self[rowi, columni] = item columni += 1 rowi += 1 def get_contents(self): raise NotImplementedError('...but could be implemented right here') def __setitem__(self, location, value): if len(location) == 2: row1, column1 = location row2, column2 = None, None else: row1, column1, row2, column2 = location self.set_contents_at(value, row1, column1, row2, column2) def __getitem__(self, location): if len(location) == 2: row1, column1 = location row2, column2 = none, none else: row1, column1, row2, column2 = location return self.get_contents_at(row1, column1, row2, column2) class Picture(Widget): # FIXME: I don't like this widget: it seems fundamentally different # from all the other widgets... def __init__(self, factory, **kwargs): Widget.__init__(self, factory, **kwargs) picture = helper.attr_accessor_indirection('picture') def set_picture(self, x): raise NotImplementedError def get_picture(self): raise NotImplementedError class WidgetFactory(object): def __init__(self): pass def do_quit(self): # FIXME: ? pass def button(self, **kwargs): raise NotImplementedError def edit(self, **kwargs): raise NotImplementedError def frame(self, **kwargs): raise NotImplementedError def label(self, **kwargs): raise NotImplementedError def newline(self, **kwargs): raise NotImplementedError def picture(self, **kwargs): raise NotImplementedError def space(self, **kwargs): raise NotImplementedError def table(self, **kwargs): raise NotImplementedError def window(self, **kwargs): raise NotImplementedError tk_windows = {} class TkThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.done = False def run(self): global tk_windows while not self.done: if tk_windows: Tkinter.mainloop() time.sleep(.1) def kill(self): # FIXME: this is a hack to abort the Tkinter.mainloop() global tk_windows self.done = True for window in tk_windows: window.quit() class TkWidgetFactory(WidgetFactory): def __init__(self): self.thread = TkThread() self.thread.start() def do_quit(self): self.thread.kill() def button(self, **kwargs): return TkButton(self, **kwargs) def edit(self, **kwargs): return TkEdit(self, **kwargs) def frame(self, **kwargs): return TkFrame(self, **kwargs) def label(self, **kwargs): return TkLabel(self, **kwargs) def newline(self, **kwargs): return NewLine(self, **kwargs) def picture(self, **kwargs): return TkPicture(self, **kwargs) def space(self, **kwargs): return TkSpace(self, **kwargs) def table(self, **kwargs): return TkTable(self, **kwargs) def window(self, **kwargs): return TkWindow(self, **kwargs) class TkWidget(object): def set_master(self, master): raise NotImplementedError('...but it SHOULD be implemented') class TkFrameMixin(TkWidget): def __init__(self): # FIXME: Expect the class this is mixed in with to have self.factory self.master = None self.tkframe = None self._title = None self._content_widgets = [] def set_title(self, text): # FIXME: update() doesn't work quite as hoped for self._title = text self.update() def set_contents(self, widgets): # FIXME: transform_contents is generic code, and should be included # in the abstract widget tree, not the widget implementation tree. self._content_widgets = transform_contents(self.factory, widgets) self.update() def set_master(self, master): if not self.master: self.master = master self.tkframe = Tkinter.Frame(master) self.update() elif master != self.master: raise Exception def update(self): # Why there are two frames (tkframe and tksubframe): # - tkframes get stacked left to right # - tksubframes implement the newline() widget by getting # stacked top to bottom. The number of tksubframes is # equal to the number of newlines + 1. # FIXME: Why isn't something this freakish done with cleanroom # techniques? # FIXME: "and self._content_widgets" is a hack if self.tkframe and self._content_widgets: label_widgets = [] if self._title: label = self.factory.label() label.set_label(self._title) label_widgets.append(label) label_widgets.append(self.factory.newline()) tksubframe = Tkinter.Frame(self.tkframe) for w in label_widgets + self._content_widgets: if w.__class__ is NewLine: tksubframe.pack(fill=Tkinter.X) tksubframe = Tkinter.Frame(self.tkframe) else: w.set_master(tksubframe) tksubframe.pack(fill=Tkinter.X) self.tkframe.pack(side=Tkinter.LEFT) self.tkframe.update() class TkFrame(Frame,TkFrameMixin): def __init__(self, factory, **kwargs): TkFrameMixin.__init__(self) Frame.__init__(self, factory, **kwargs) def set_master(self, master): if not self.master: self.master = master self.tkframe = Tkinter.Frame(master, borderwidth=0, relief=Tkinter.RIDGE) self.update() elif master != self.master: raise Exception def set_title(self, text): TkFrameMixin.set_title(self, text) def set_contents(self, widgets): self._content_widgets = [] if self.master: self.tkframe.destroy() master = self.master self.master = None self.set_master(master) TkFrameMixin.set_contents(self, widgets) class TkWindow(Window,TkWidget): def __init__(self, factory, **kwargs): global tk_windows # FIXME: hack to abort Tkinter.mainloop() Window.__init__(self, factory, **kwargs) self.tkwindow = Tkinter.Tk() self.frame = TkFrame(factory) self.frame.set_master(self.tkwindow) tk_windows[self] = None # FIXME: (read above) def quit(self): # FIXME: this is a hack to abort Tkinter.mainloop() self.tkwindow.quit() def set_title(self, text): raise NotImplementedError def set_contents(self, contents): self.frame.contents = contents def get_contents(self): return self.frame.contents class TkButton(Button,TkWidget): def __init__(self, factory, **kwargs): self.master = None self.tkbutton = None self.tkstringvar = None self._text = None Button.__init__(self, factory, **kwargs) def set_master(self, master): if not self.master: self.master = master self.tkstringvar = Tkinter.StringVar(self.master) self.tkbutton = Tkinter.Button(master, textvariable = self.tkstringvar, command = self.tkcallback) self.update() elif master != self.master: raise Exception def set_text(self, text): self._text = text self.update() def update(self): if self.tkbutton: self.tkstringvar.set(self._text) self.tkbutton.pack(side=Tkinter.LEFT) def tkcallback(self): self.NotifyObservers() #print self class TkLabel(Label,TkWidget): def __init__(self, factory, image=None, **kwargs): self.master = None self.tklabel = None self.tkstringvar = None self._text = None self._image = image Label.__init__(self, factory, **kwargs) def set_master(self, master): if not self.master: self.master = master self.tkstringvar = Tkinter.StringVar(self.master) self.tklabel = Tkinter.Label(master, textvariable = self.tkstringvar, image=self._image) self.update() elif master != self.master: if global_debug: raise Exception # FIXME def set_text(self, text): self._text = text self.update() def update(self): if self.tklabel: self.tkstringvar.set(self._text) self.tklabel.pack(side=Tkinter.LEFT) def tkcallback(self): print self class TkEdit(Edit,TkWidget): def __init__(self, factory, **kwargs): self.master = None self.tkentry = None self.tkstringvar = None self._text = ' ' Edit.__init__(self, factory, **kwargs) def set_master(self, master): if not self.master: self.master = master self.tkstringvar = Tkinter.StringVar(self.master) self.tkstringvar.trace_variable('w', self.tkcallback) self.tkentry = Tkinter.Entry(master, textvariable = self.tkstringvar) self.update() elif master != self.master: raise Exception def set_text(self, text): self._text = text def update(self): if self.tkentry: if self._text != self.tkstringvar.get(): self.tkstringvar.set(self._text) self.tkentry.pack(side=Tkinter.LEFT) def tkcallback(self, *args): self._text = self.tkstringvar.get() self.NotifyObservers() #print self, self._text class TkSpace(Space,TkWidget): def __init__(self, factory, **kwargs): self.label_widget = factory.label() Space.__init__(self, factory, **kwargs) def set_ems(self, ems): self.label_widget.text = ' ' * int(ems*2) # FIXME: ;-) def set_master(self, master): self.label_widget.set_master(master) def update(self): return self.label_widget.update() def tkcallback(self): print self class TkTable(Table,TkWidget): def __init__(self, factory, **kwargs): self.tkframe = None self._contents = {} self.subframes = {} self.master = None Table.__init__(self, factory, **kwargs) def set_master(self, master): if not self.master: self.master = master self.tkframe = Tkinter.Frame(master, borderwidth=2) self.update() elif master != self.master: raise Exception def set_rows(self, rows): raise NotImplementedError def get_rows(self): return self._rows def set_columns(self, columns): raise NotImplementedError def get_columns(self): return self._columns def set_contents_at(self, contents, row1, column1, row2, column2): key = (row1, column1) if row2 is None: rowspan = 1 else: rowspan = row2 - row1 + 1 if column2 is None: columnspan = 1 else: columnspan = column2 - column1 + 1 self._contents[key] = ((rowspan, columnspan), contents) self.update() def get_contents_at(self, row1, column1, row2, column2): # FIXME: how to handle contents inside a {row,column}span? key = (row1, column1) if key in self._contents: return self._contents[1] else: return [] def update(self): if self.tkframe: for key in self._contents: row, column = key (rowspan, columnspan), contents = self._contents[key] if key in self.subframes: subsubframe = self.subframes[key] else: subframe = Tkinter.Frame(self.tkframe, borderwidth=2) subframe.grid(row=row, column=column, rowspan=rowspan, columnspan=columnspan) subsubframe = self.factory.frame() self.subframes[key] = subsubframe subsubframe.set_master(subframe) subsubframe.set_contents(contents) self.tkframe.pack() # INFO: to solve the `TclError: image "pyimage1" doesn't exist' bug # Some comments... # - a reference to images needs to be kept around... # - a common reason for this is that you have multiple Tk instances in # your application... # - (a) use Toplevel (v. Tk) to create multiple windows # - (b) set master=... appropriately when creating stuff class TkPicture(Picture,TkWidget): def __init__(self, factory, **kwargs): self.frame = factory.frame() self._master = None self._picture = None self._oldpicture = None Picture.__init__(self, factory, **kwargs) def set_master(self, master): self.frame.set_master(master) self._master = master self.update() def set_picture(self, x): self._picture = x self.update() def get_picture(self): return self._picture def update(self): # FIXME: do this through self.factory.label()...? ...naah! if self._master and self._picture is not self._oldpicture: image = ImageTk.PhotoImage(self._picture, master=self._master) label = TkLabel(self.factory, image=image) self.frame.set_contents([label]) self._oldpicture = self._picture def test_tk(): import Image class Context(object): pass ctxt = Context ctxt.done = False #done = False factory = TkWidgetFactory() window = factory.window() label = factory.label() edit1 = factory.edit() button1 = factory.button() button2 = factory.button() button3 = factory.button() button4 = factory.button() quit_button = factory.button() newline = factory.newline() frame1 = factory.frame() tbutton1 = factory.button(label='tbutton1') tbutton2 = factory.button() tbutton3 = factory.button() tbutton4 = factory.button() tbutton5 = factory.button() picture = factory.picture() table = factory.table() def edit_callback(observer): print 'Aha! I saw you change the text:', observer.Subject.text def button_callback(observer): print 'I felt you touching the "%s" button!' % observer.Subject.text def quit_callback(observer): #done = True # this won't work: not a real closure ctxt.done = True factory.do_quit() patterns.CallbackObserver(subject=edit1, callback=edit_callback) patterns.CallbackObserver(subject=button1, callback=button_callback) patterns.CallbackObserver(subject=button2, callback=button_callback) patterns.CallbackObserver(subject=button3, callback=button_callback) patterns.CallbackObserver(subject=button4, callback=button_callback) patterns.CallbackObserver(subject=quit_button, callback=quit_callback) button1.text = 'hurray' label.text = 'my foo label' button2.text = 'yay' button3.text = 'go' quit_button.text = 'Quit' table[0, 0] = [tbutton1] table[0, 1] = [tbutton2] table[1, 0] = [Image.open('i-want-gnu.png'), picture] table[1, 1] = [tbutton3, tbutton4, newline, tbutton5] frame1.set_contents([ button4, edit1, ]) window.set_contents([ button1, label, button2, newline, frame1, newline, button3, table, newline, Image.open('i-want-gnu.png'), newline, Image.open('i-want-gnu.png'), newline, quit_button ]) time.sleep(.3) button1.text = 'hurray 1' time.sleep(.3) button1.text = 'hurray 12' time.sleep(.3) button1.text = 'hurray 123' im1 = Image.open('weiner-dog.jpg') im2 = Image.open('i-want-gnu.png') while not ctxt.done: picture.picture = im1 time.sleep(1) picture.picture = im2 time.sleep(1) print 'ctxt.done =', ctxt.done if __name__ == '__main__': test_tk()