#!/usr/bin/python # # Import various useful libraries # import Tkinter as Tk import dircache import os.path import sys import tkFont import giepy as gp import math import tkColorChooser as tkcc import color as cr import random import time import threading import string as stg class iVar(Tk.IntVar): # Just like a Tk.IntVar, but you can set the value at initialization var = iVar(x) and it is easier to trace def __init__(self, value=None): Tk.IntVar.__init__(self) if value != None: self.set(value) def trace(self, command, mode='w', **keyw): # Makes it easier to trace changes to Tk variables. command should be a function that should be called when the value of # this Tk variable is changed, and should take this Tk variable object (self) as the first argument. Any additional # named arguments to trace are supplied to command when it is called. self.trace_arguments = keyw self.trace_command = command self.trace_id = self.trace_variable(mode, self._trace_wrap_command) def _trace_wrap_command(self, varname, tk_data, mode): self.trace_command(self, **self.trace_arguments) def stop_trace(self): # stops the current trace try: self.trace_vdelete(self.trace_id) except AttributeError: pass class dVar(Tk.DoubleVar): # Just like a Tk.DoubleVar, but you can set the value at initialization var = dVar(x) and it is easier to trace def __init__(self, value=None): Tk.DoubleVar.__init__(self) if value != None: self.set(value) def trace(self, command, mode='w', **keyw): # Makes it easier to trace changes to Tk variables. command should be a function that should be called when the value of # this Tk variable is changed, and should take this Tk variable object (self) as the first argument. Any additional # named arguments to trace are supplied to command when it is called. self.trace_arguments = keyw self.trace_command = command self.trace_id = self.trace_variable(mode, self._trace_wrap_command) def _trace_wrap_command(self, varname, tk_data, mode): self.trace_command(self, **self.trace_arguments) def stop_trace(self): # stops the current trace try: self.trace_vdelete(self.trace_id) except AttributeError: pass class fVar(dVar): # Just like dVar, but I sometimes prefer this name (f for float) pass class sVar(Tk.StringVar): # Just like a Tk.StringVar, but you can set the value at initialization var = sVar(x) and it is easier to trace def __init__(self, value=None): Tk.StringVar.__init__(self) if value != None: self.set(value) def trace(self, command, mode='w', **keyw): # Makes it easier to trace changes to Tk variables. command should be a function that should be called when the value of # this Tk variable is changed, and should take this Tk variable object (self) as the first argument. Any additional # named arguments to trace are supplied to command when it is called. self.trace_arguments = keyw self.trace_command = command self.trace_id = self.trace_variable(mode, self._trace_wrap_command) def _trace_wrap_command(self, varname, tk_data, mode): self.trace_command(self, **self.trace_arguments) def stop_trace(self): # stops the current trace try: self.trace_vdelete(self.trace_id) except AttributeError: pass class EasyListbox(Tk.Frame): # This is like the Tk.Listbox widget, but you can add the items and scrollbars to it at initialization time. # This is a frame widget, but you can call listbox methods on it as long as Tk.Frame doesn't also have those methods. # The listbox can be accessed directly as self.listbox. # There are also handy functions to retrieve the selected values or the indicies of the selected values # def __getattr__(self, attribute): # This is called if a method of this class is called that does not exist. If the method exists for the child widget, we # return that one instead. That is why in addition to calling child widget methods using self.child.method, you can also just # use self.method if hasattr(Tk.Listbox, attribute): # Must not reference self.child in the hasattr above, because __getattr__ is defined before __init__, so self.child # doesn't exist yet. But it will exist by the time any attributes are called for that child has. return getattr(self.listbox, attribute) else: raise AttributeError def __init__(self, parent, items=None, scrollx=True, scrolly=True, width=None, height=None, initial=None, **kwarg): # parent = containfer for this widget # items = list of items to be added to the listbox # scrollx = True if a horizontal scrollbar should be used # scrolly = True if a vertical scrollbar should be used # width, height = width and height of the listbox # initial = the initial item to be selected in the listbox # # components: # self.listbox # self.scrollx, self.scrolly (if requested) Tk.Frame.__init__(self, parent) if not height: try: height = len(items) except TypeError: height = 10 if not width: try: width = int(1.20 * max([len(str(x)) for x in items])) except TypeError: width = 20 self.listbox = Tk.Listbox(self, height=height, width=width, **kwarg) self.listbox.grid(row=0, column=0, sticky=Tk.E+Tk.W+Tk.N+Tk.S) if scrollx: self.scrollx = Tk.Scrollbar(self, orient=Tk.HORIZONTAL, command=self.listbox.xview) self.listbox.config(xscrollcommand=self.scrollx.set) self.scrollx.grid(row=height+1, column=0, sticky=Tk.E+Tk.W) if scrolly: self.scrolly = Tk.Scrollbar(self, orient=Tk.VERTICAL, command=self.listbox.yview) self.listbox.config(yscrollcommand=self.scrolly.set) self.scrolly.grid(row=0, column=1, rowspan=height, sticky=Tk.N+Tk.S) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) try: for item in items: self.insert(Tk.END, item) try: self.selection_set(items.index(initial)) except ValueError: pass except TypeError: pass def get_values(self): # Returns a single selected value if selection type is "BROWSE" or "SINGLE", else a (possibly empty) list of selected values values = [ self.get(line) for line in self.curselection() ] if self.listbox.cget('selectmode') in [Tk.BROWSE, Tk.SINGLE]: try: return values[0] except IndexError: return "" else: return values def get_indexes(self): # Returns the index of the single selection as an integer if selection type is "BROWSE" or "SINGLE" (-1 if nothing selected) # else a (possibly empty) list of integer selected values. values = [ int(line) for line in self.curselection() ] if self.listbox.cget('selectmode') in [Tk.BROWSE, Tk.SINGLE]: try: return values[0] except IndexError: return -1 else: return values def get_indicies(self): return self.get_indexes() class EasyMenuBar(Tk.Menu): # # Easily creates a menu bar with a set of menus for the parent widget. # def __init__(self, parent=None, menus=None, order=None, tearoff=0): # parent should be the parent widget for this menubar # menus should be a dictionary: # Keys are the labels for each menu # Values are a LIST of tuples(label, command_or_menu, [type], [variable], [value], [config_option_dict]) for the menu Key # command_or_menu is the command to call for this menu item, or a menu item if this is type cascade # type='cascade', 'checkbutton', 'command', 'radiobutton' or 'separator' # if type is not included, 'command' is the default # variable = the control variable for radiobuttons and checkbuttons # value = the initial value of the variable # if the last value in the tuple is a dictionary, it is considered to be a dictionary of configuration options # # Example: # menubar = gw.EasyMenuBar(parent=root, order=['File', 'Edit', 'Prefs', 'Help'], # menus={'File': [('Open Gaussian Output', open_file), # ('Save XY Data', save_spectrum), # ('Remove Spectrum', remove_spectrum), # ('Quit', root.quit)], # 'Edit': [('Copy Plot', copy_plot)], # 'Prefs': [('Bandwidth', get_bandwidth), # ('Frequency Type', get_freq_type), # ('Vibration Amplitude', get_vib_amp), # ('Vibration Speed', get_vib_speed), # ('Molecule Size', get_mol_size), # ('Frequency Lines', toggle_frequency_lines, 'checkbutton', frequency_lines, 1)], # 'Help': [('Help', give_help), # ('About', show_about)] # }) # # order is a list of the menus in the order they should appear from left to right # tearoff = 1 if the menu can be torn off # Tk.Menu.__init__(self) if parent: parent.config(menu=self) self.menus = {} self.tearoff = tearoff if hasattr(menus, 'ordered_keys'): do_list = menus.ordered_keys() elif order: do_list = order else: do_list = menus.keys() for amenu in do_list: self.menus[amenu] = Tk.Menu(tearoff=tearoff) self.add_cascade(label=amenu, menu=self.menus[amenu]) for item in menus[amenu]: if isinstance(item[-1], dict): config_dict = item[-1] item = item[:-1] else: config_dict = {} try: mytype = item[2] except IndexError: mytype = 'command' if mytype == 'cascade': self.menus[amenu].add_cascade(label=item[0], menu=item[1]) elif mytype == 'command': self.menus[amenu].add_command(label=item[0], command=item[1]) elif mytype == 'checkbutton': self.menus[amenu].add_checkbutton(label=item[0], command=item[1], variable=item[3]) elif mytype == 'radiobutton': self.menus[amenu].add_radiobutton(label=item[0], command=item[1], variable=item[3], value=item[4]) elif mytype == 'separator': self.menus[amenu].add_separator() else: raise TypeError self.menus[amenu].entryconfigure(self.menus[amenu].index(item[0]), config_dict) def menu_index(self, menu): # Returns the index of the menu object with the label 'name' return self.index(menu) def submenu_index(self, menu, name): # Returns the index of the submenu object in the 'menu' menu with the label 'name' return self.menus[menu].index(name) def menu(self, menu): # Returns the menu object with the label 'menu' return self.menus[menu] class EasyScale(Tk.Scale): # # Like Tk.Scale, only you can set the value at creation time # value = initial value for the scale # def __init__(self, master, value=None, **keyw): Tk.Scale.__init__(self, master, **keyw) if value: mystate = self.cget('state') self.config(state=Tk.NORMAL) self.set(value) self.config(state=mystate) class EasyCanvasObject(object): # # Base class for EasyCanvas objects such as rectangles, circles, moveable text, etc. # def __new__(cls, creator, *args, **keyw): if creator == Tk.Canvas.create_window: # Window objects should be created via EasyCanvasWindow() class # EasyCanvasWindow.__init__ will get called upon create of the EasyCanvasWindow object, but it will also get called # again after this routine ends, because it returns an instance of EasyCanvasWindow, which is a subclass of # EasyCanvasObject. This is a rather convoluted way to create a class factory, but I wanted to explore how __new__ # works. An easier way would be for EasyCanvas.create_window to simply create an instance of EasyCanvasWindow instead return EasyCanvasWindow(creator, *args, **keyw) else: return object.__new__(cls) # Base class for making all EasyCanvas objects. We don't know how many arguments we'll get, because we don't know how many coords # there will be. Thus we have to use the cumbersome *args/**keyw/keyw.pop() syntax. keyw.pop() grabs the keywords that aren't # Tk.Canvas object keywords and removes them so there is no error thrown by Tk.Canvas.create_* # # This class is also useful for putting movable objects onto regular Tk.Canvas objects. However, the item returned is the # EasyCanvasObject instance, not the canvas ID. easycanvasobject.id is the canvas id. # Call with EasyCanvasObject(Tk.Canvas.create_xxx, canvas_instance_to_create_on, canvas_object_arguments, glitter) # where: xxx is arc, line, oval, etc. and do not include the () (i.e. pass in the function, not the function call # canvas_instance is the canvas the object should be created on # canvas_object_arguments are the normal arguments to the Canvas.create_xxx call (coordinates, options, etc.) # glitter = the extra keyword arguments allowed by the EasyCanvasObject class (see comments in the __init__ routine) def __init__(self, creator, canvas, *args, **keyw): try: # __init__ gets called twice for EasyCanvasWindow objects because of the way __new__ works. Don't do anything the # second time. if self.id: return except AttributeError: # extra allowed arguments: # moveable=True or False:whether the item can be dragged around the canvas by the user # button=# (mouse button required to drag this item if it is moveable, default is 1 # respect=True or False:whether the item must respect the canvas boundaries (True) or can be dragged off-canvas # # sets self.id = canvas object ID of this object self.canvas = canvas moveable = keyw.pop('moveable', False) self.button = str(keyw.pop('button', 1)) self.respect = keyw.pop('respect', True) self.id = creator(canvas, *args, **keyw) self.move_x = self.move_y = None self.is_moveable = False if moveable: self.make_moveable() def binding_strings(self, button): # returns the event strings for mouse motion and button release events # button = mouse button # if button != None: self.button = str(button) event_start_str = '' event_end_str = '' return event_start_str, event_end_str def make_moveable(self, button=None): # binds mouse movement and release events so I can be moved event_start_str, event_end_str = self.binding_strings(button) self.canvas.tag_bind(self.id, event_start_str, self.move_me) self.canvas.tag_bind(self.id, event_end_str, self.stop_me) self.is_moveable = True def move_me(self, event): # # Takes care of moving the object when the user is dragging it around # if self.move_x == None: self.move_x = event.x self.move_y = event.y else: movex = event.x - self.move_x movey = event.y - self.move_y if self.canvas.type(self.id) != 'window': # For canvas windows, the event.x,y are relative to the interior widget and remain constant during the move # For all other canvas objects, the event.x,y are relative to the canvas itself and change to reflect the # mouse movement during the move. Therefore, we want to keep track of the current object position self.move_x = self.move_x + movex self.move_y = self.move_y + movey self.canvas.move(self.id, movex, movey) if self.respect: # Don't allow the object to move off the screen x0, y0, x1, y1 = self.canvas.bbox(self.id) bd = int(self.canvas.cget('highlightthickness')) correction_x = correction_y = 0 if x0 < bd: correction_x = bd - x0 elif x1 > int(self.canvas.cget('width')) + bd: correction_x = int(self.canvas.cget('width')) + bd - x1 if y0 < bd: correction_y = bd - y0 elif y1 > int(self.canvas.cget('height')) + bd: correction_y = int(self.canvas.cget('height')) + bd - y1 if correction_x or correction_y: self.canvas.move(self.id, correction_x, correction_y) def stop_me(self, event): # Terminates movement self.move_x = self.move_y = None def make_unmoveable(self, button=None): # Unbinds the mouse movement events so I can't be moved by the user anymore event_start_str, event_end_str = self.binding_strings(button) self.canvas.tag_unbind(self.id, event_start_str, self.move_me) self.canvas.tag_unbind(self.id, event_end_str, self.stop_me) self.is_moveable = False def delete_me(self): # Takes care of anything I need to do before I am deleted self.make_unmoveable() class EasyCanvasWindow(EasyCanvasObject): # # Special class for window objects for the EasyCanvas class # def __new__(cls, *args, **keyw): # We have to un-overwrite the __new__ method to get rid of the EasyCanvasObject method return object.__new__(cls) def make_moveable(self, button=None): # Window objects need their own make_moveable routine because we bind to the interior widget rather than the object itself event_start_str, event_end_str = self.binding_strings(button) try: widget = self.canvas.nametowidget(self.canvas.itemcget(self.id, 'window')) widget.bind(event_start_str, self.move_me) widget.bind(event_end_str, self.stop_me) self.is_moveable = True except KeyError: pass def make_unmoveable(self, button=None): # Window objects need their own make_unmoveable routine because we bind to the interior widget rather than the object itself event_start_str, event_end_str = self.binding_strings(button) try: widget = self.canvas.nametowidget(self.canvas.itemcget(self.id, 'window')) widget.unbind(event_start_str) widget.unbind(event_end_str) self.is_moveable = False except KeyError: pass class EasyCanvas(Tk.Canvas): # # Like a Tk.Canvas object, but with several advantages # Each object on the canvas can be created as moveable simply by adding the moveable=True flag to the canvas.create_xxx call # moveable=True or False:whether the item can be dragged around the canvas by the user # button=# (mouse button required to drag this item if it is moveable, default is 1 # respect=True or False:whether the item must respect the canvas boundaries (True) or can be dragged off-canvas # Methods to easily create text that the user can edit, including font, size, wieght, location, etc. # create_circle(x, y, radius=n) method # # Unless doing something special as mentioned above, this acts just like a normal canvas object with normal canvas methods # def __init__(self, master, **keyw): Tk.Canvas.__init__(self, master, **keyw) self.master = master self.easy_items = gp.RichDict() self.uct_onetime = True self.uct_event = '' def delete(self, item): # We need a special delete method to remove all the easy_item objects since they are now class instances for id in self.find_withtag(item): # Using find_withtag makes sure we catch all items whether the user specifies a tag or an id easy_item = self.easy_items.pop(item, None) if easy_item: easy_item.delete_me() Tk.Canvas.delete(self, item) def create_window(self, x, y, widget=None, **keyw): if widget: keyw['window'] = widget window = EasyCanvasObject(Tk.Canvas.create_window, self, x, y, **keyw) self.easy_items[window.id] = window return window.id def create_arc(self, *args, **keyw): my_object = EasyCanvasObject(Tk.Canvas.create_arc, self, *args, **keyw) self.easy_items[my_object.id] = my_object return my_object.id def create_bitmap(self, *args, **keyw): my_object = EasyCanvasObject(Tk.Canvas.create_bitmap, self, *args, **keyw) self.easy_items[my_object.id] = my_object return my_object.id def create_image(self, *args, **keyw): my_object = EasyCanvasObject(Tk.Canvas.create_image, self, *args, **keyw) self.easy_items[my_object.id] = my_object return my_object.id def create_line(self, *args, **keyw): my_object = EasyCanvasObject(Tk.Canvas.create_line, self, *args, **keyw) self.easy_items[my_object.id] = my_object return my_object.id def create_polygon(self, *args, **keyw): my_object = EasyCanvasObject(Tk.Canvas.create_polygon, self, *args, **keyw) self.easy_items[my_object.id] = my_object return my_object.id def create_rectangle(self, *args, **keyw): my_object = EasyCanvasObject(Tk.Canvas.create_rectangle, self, *args, **keyw) self.easy_items[my_object.id] = my_object return my_object.id def create_text(self, *args, **keyw): my_object = EasyCanvasObject(Tk.Canvas.create_text, self, *args, **keyw) self.easy_items[my_object.id] = my_object return my_object.id def create_circle(self, x, y, radius=1, **keyw): # The circle object is actually an oval object with coords x-radius-1, y-radius-1, x+radius, y+radius # (the -1 comes about because the radius counts the center pixel, so the outer edge should be radius-1 pixels away - we do # not need to add radius-1 to x & y because the way the canvas draws ovals is different for the left and top than for the # right and bottom sides) # To assign it new coordinates via canvas.coords(), you need to feed it all four coords. canvas.move() works as normal circle = EasyCanvasObject(Tk.Canvas.create_oval, self, x-radius-1, y-radius-1, x+radius, y+radius, **keyw) self.easy_items[circle.id] = circle return circle.id def binding_string_press(self, button): button_str = str(button) event_start_str = '' return event_start_str def initiate_user_created_text(self, event_string='', onetime=True): # Call this method to prep the canvas to create user text at the next location the user performs "event_string" at # event_string = the event to watch for to indicate we should create the user text (see self.create_user_text) # onetime = True if we should unbind this event after the user performs it once, false if the event should always generate # a user text instance self.bind(event_string, self.create_user_text) self.config(cursor='xterm') self.uct_onetime = onetime def delete_user_text(self, event): mytext = self.find_closest(event.x, event.y) while self.type(mytext) != 'text': mytext = self.find_closest(event.x, event.y, start=mytext) self.delete(mytext) def create_user_text(self, event, del_button=3): # Performs the work of allowing a user to create text on the canvas. # The text can be moved by dragging with the left mouse button # del_button = if non-zero, the button the user clicks the text with to delete it def form_text(): mytext = entry.get() if check_dict['bold'].get(): bold='bold' else: bold='normal' if check_dict['italic'].get(): italic='italic' else: italic='roman' try: font = tkFont.Font(family=fontbox.get(), size=int(sizebox.get()), weight=bold, slant=italic, underline=check_dict['underline'].get(), overstrike=check_dict['strikethrough'].get()) except ValueError: pass if mytext: thistext = self.create_text(event.x, event.y, moveable=True, text=mytext, font=font, fill=colorbox.get()) if del_button: self.tag_bind(thistext, "", self.delete_user_text) input = ModalWindow(self.master, buttons=True, coords=(event.x_root-40, event.y_root-40), user_accept=form_text) entry = input.append(SuperEntry(input, label_text='Text', label_pack_args={'side' : Tk.TOP, 'anchor' : Tk.W}, width=40)) option_frame = input.append(Tk.Frame(input), pady=8) drop_frame = Tk.Frame(option_frame) fontbox = DropDownCombobox(drop_frame, items=['Arial', 'Courier', 'Helvetica', 'Script', 'Small Fonts', 'Symbol', 'Times New Roman'], initial='Helvetica') sizebox = DropDownCombobox(drop_frame, items=[ x for x in xrange(6, 64, 2) ], height=10, initial=12, user_edit=True) colorbox = DropDownCombobox(drop_frame, items=['black', 'white', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', 'grey'], initial='black') type_frame = Tk.Frame(option_frame) check_dict = {} for type in ['bold', 'italic', 'underline', 'strikethrough']: check_dict[type] = EasyCheckbutton(type_frame, text=type) check_dict[type].pack(side=Tk.TOP, anchor=Tk.W) fontbox.pack(side=Tk.TOP, anchor=Tk.W) sizebox.pack(side=Tk.TOP, anchor=Tk.W, pady=10) colorbox.pack(side=Tk.TOP, anchor=Tk.W) drop_frame.pack(side=Tk.LEFT, anchor=Tk.N) type_frame.pack(side=Tk.RIGHT, anchor=Tk.N, padx=8) input.pack_and_display(grab=False) if self.uct_onetime: self.unbind(self.uct_event) self.config(cursor="") class EasyText(Tk.Text): # # Like a Tk.Text widget, but with lots of improvements - see the methods below for what they are. # To get scrollbars, use the ScrollingText class, which returns an EasyText with scrollbars inside a frame # def __init__(self, master, text=None, tags=None, *args, **keyw): # # text = contents of the widget # tags = tags to assign to text Tk.Text.__init__(self, master, *args, **keyw) if text: if self.cget('state') == Tk.DISABLED: self.secure_setall(text, tags=tags) else: self.setall(text, tags=tags) # self.found_index and self.old_text are used in the search function self.found_index = "1.0" self.old_text = "" def new_tag(self, tag, **configure_options): # Allows the user to add a new tag and configure it without assigning it to any specific text self.tag_add(tag, '1.0') self.tag_remove(tag, '1.0') if configure_options: self.tag_config(tag, **configure_options) def tag_all(self): # Makes sure all the text has the EasyText tag self.tag_only('EasyText', begin="1.0", end=Tk.END) def config_all(self, **keyw): # configure all the text in the widget self.tag_all() self.tag_config('EasyText', **keyw) def enable(self): # Enable the widget for writing self.config(state=Tk.NORMAL) def disable(self): # Disable the widget for writing self.config(state=Tk.DISABLED) def getall(self): # Return all the text in the widget data = self.get('1.0', Tk.END) return data def combine_tags(self, tag, tags): # This allows the user to add a single tag as a simple string with the tag parameter, or multiple tags as either a tuple # or a list. Combining them as a list first before converting to a tuple gets around the problem that it is hard to convert # a single string to a tuple (since ('bob') = ('b', 'o', 'b') def tag_to_list(some_tags): mytags = [] if isinstance(some_tags, (list, tuple)): for atag in some_tags: mytags.append(atag) elif some_tags: mytags.append(some_tags) return mytags mytags = tuple(tag_to_list(tag) + tag_to_list(tags)) return mytags def insert(self, index, text, tags=None, tag=None): tags = self.combine_tags(tag, tags) # This must be a call to Tk.Text.insert, since we are overwriting our own insert method Tk.Text.insert(self, index, text, tags) self.tag_all() def append(self, text, tags=None, tag=None): # Adds text to the end of the widget tags = self.combine_tags(tag, tags) self.insert(Tk.END, text, tags) self.tag_all() def append_line(self, text, tags=None, tag=None): # Adds a line to the end of the widget, including a newline if text[-1] != '\n': text = text + '\n' tags = self.combine_tags(tag, tags) self.insert(Tk.END, text, tags) self.tag_all() def setall(self, text, tags=None, tag=None): # sets text as the entire content of the widget self.clear() self.insert('1.0', text, tags=tags, tag=tag) self.tag_all() def set(self, *arg, **keyw): # another name for insert self.insert(*arg, **keyw) def clear(self, tags=False): # Erases the entire widget and removes all tags self.delete('1.0', Tk.END) if tags: for atag in self.tag_names(): self.tag_delete(atag) def delete_by_tag(self, tag): # Deletes all the text in the widget tagged with 'tag' index = '1.0' range = self.tag_nextrange(tag, index) while range: self.delete(range[0], range[1]) index = range[0] range = self.tag_nextrange(tag, index) def secure_setall(self, text, tags=None, tag=None): # Sets the entire widget contents to text and then disables it self.enable() self.clear() self.insert('1.0', text, tags=tags, tag=tag) self.disable() self.tag_all() def secure_set(self, index, text, tags=None, tag=None): # Inserts text at index and then disables the widget self.enable() self.insert(index, text, tags=tags, tag=tag) self.disable() self.tag_all() def tag_only(self, tag, begin="1.0", end=None): # Makes it so only the text between begin and end has the tag of 'tag' self.tag_remove(tag, "1.0", Tk.END) self.tag_add(tag, begin, end) def int_row_col_index(self, index): # Returns integer row and column values from any index text_index = self.index(index) row = int(text_index.split('.')[0]) col = int(text_index.split('.')[1]) return row, col def text_index(self, row, column): # returns the index of a given row and column return '.'.join([str(row), str(column)]) def show_range(self, begin, end): # Tries to show the whole range from begin to end by showing begin and then scrolling to end self.see(begin) self.see(end) def select(self, begin, end): # Selects the range from begin to end self.tag_only(Tk.SEL, begin, end) def get_word_indicies(self, index, start_chars=None, end_chars=None): # Returns the entire word surrounding index. A word is defined as starting at the start of the line or after one of the # characters in start_chars. It ends just before one of the characters in end_chars or the end of the line. # start_chars and end_chars should be strings if not start_chars: start_chars = stg.whitespace + '({[$"' + "'" if not end_chars: end_chars = stg.whitespace + ',.;:?!*)]}%"' + "'" # We make sure that '\n' is in the end_chairs list, since that is the character that text widgets return when you go past the # end of the line. end_chars = end_chars + '\n' row, column = self.int_row_col_index(index) # Find the start of the word start_column = 0 count = column - 1 while count > -1: if self.get(self.text_index(row, count)) in start_chars: start_column = count + 1 break count = count - 1 # Find the end of the word. Indicies beyond the end of the line contain whitespace. end_column = column count = column while count < column+1000: if self.get(self.text_index(row, count)) in end_chars: end_column = count break count = count + 1 return self.text_index(row, start_column), self.text_index(row, end_column) def search_it(self, stext, word=False, start_chars=None, end_chars=None): # Search for, highlight and show the next occurrence of the string 'stext' # Keeps track of where it is so it can progress through the text widget looking for the next occurrence with subsequent calls # If word, then highlight the whole word containing stext using start_chars and end_chars passed on to get_word_indicies # Returns the beginning and ending index of the match if stext == "": return if stext != self.old_text: # A new string, start searching at the cursor if it exists, or at the beginning self.found_index = self.index(Tk.INSERT) else: # Increment the current index so we don't find the same occurrence again if self.found_index: row, column = self.int_row_col_index(self.found_index) self.found_index = self.text_index(row, column+1) old_index = self.found_index self.found_index = self.search(stext, self.found_index, nocase=1) if self.found_index == "1.0" and old_index != "1.0": # No more occurrences, start again at the beginning self.found_index = self.search(stext, self.found_index, nocase=1) if self.found_index: # First, select the occurrence. We need to set focus or the highlight doesn't show up. self.focus_set() if word: begin, end = self.get_word_indicies(self.found_index, start_chars=start_chars, end_chars=end_chars) else: begin = self.found_index row, column = self.int_row_col_index(self.found_index) end = self.text_index(row, column+len(stext)) self.select(begin, end) # Now show it - two see commands are used to make sure that the whole string shows up on screen self.show_range(begin, end) else: self.found_index = old_index begin = end = "" self.old_text = stext return begin, end class EasyCheckbutton(Tk.Checkbutton): # # Like a Tk.Checkbutton, but removes the need to separately declare a Tk variable, can be set at creation time, # and adds a few convenient methods # def __init__(self, master, init=0, anchor=Tk.W, text="", **keyw): self.var = iVar(init) Tk.Checkbutton.__init__(self, master, variable=self.var, anchor=anchor, text=text, **keyw) def get(self): return self.var.get() def on(self): self.select() def off(self): self.deselect() def is_on(self): return bool(self.get()) def is_off(self): return bool(not self.get()) class EasyCheckbox(EasyCheckbutton): # # I can never remember whether it is Checkbox or Checkbutton # pass class EasyRadiobuttons(Tk.LabelFrame): def __init__(self, master, labels, init=0, vertical=True, text="", relief=Tk.FLAT, bd=0, **keyw): # Does the work of setting up a group of radio buttons. Returns a frame with packed radiobuttons # vertical = True if the buttons go vertically, False if they are horizontally # labels = list of labels for the buttons, one button is created for each label, and EasyRadioButtons.get() returns the # index of the label that is currently selected # init = index of the initial selection # # There is no need to create a Tk variable before calling this, this is done "in house" now # self.var = Tk variable associated with the checkbox # self.labels = list of labels for the buttons # Each button can individually be accessed via self.rb['label'] # if not text: Tk.LabelFrame.__init__(self, master, relief=relief, bd=bd) else: Tk.LabelFrame.__init__(self, master, text=text) self.var = iVar(init) if vertical: anchor = Tk.W side = Tk.TOP else: anchor = Tk.N side = Tk.LEFT self.labels = labels self.rb = {} for x, label in enumerate(self.labels): self.rb[label] = Tk.Radiobutton(self, variable=self.var, value=x, text=label, **keyw) self.rb[label].pack(side=side, anchor=anchor) def get(self): # Returns the index of the selected radiobutton return self.var.get() def set_by_value(self, value): # Select a radio button by index self.var.set(value) self.rb[self.labels[value]].invoke() def set_by_label(self, label): # Select a radio button by its label self.rb[label].invoke() class PlotSeries(object): # # Helper class for the GenericPlot class # def __init__(self, chart, points, name='generic', line=False, style='square', border='black', fill='black', size=3, line_smooth=False, line_color=None, line_width=2, line_dash=False, legend=True): # chart = the chart for this series to be plotted on # points = list of (x, y) points to plot # line = connect points with a line # style = square, circle, triangle, diamond, or None (the shape of the point marker) # border = color of the border of the marker # fill = color of the interior of the marker ("" for no fill) # size = size in pixels of the marker (radius of the circle, 1/2 length of side of the square, etc.) # line_smooth = True to make the line connecting the points smooth # line_color = color of the line (default is the same size as the marker border) # line_width = width of the connecting line # line_dash = False if solid, True if dashed # legend = True if this series should be included in the legend, False if not gp.params_to_attributes(self, ['chart', 'points', 'name', 'line', 'style', 'border', 'fill', 'size', 'line_smooth', 'line_color', 'line_width', 'line_dash', 'legend'], locals()) self.tag = valid_tag(self.name) self.style_list = ['square', 'circle', 'triangle', 'diamond', None] self.assign_marker_function() if not self.line_color: self.line_color = self.border xvals = [ x for x, y in points ] yvals = [ y for x, y in points ] self.xmax = max(xvals) self.xmin = min(xvals) self.ymax = max(yvals) self.ymin = min(yvals) self.chart.tag_bind(self.tag, '', self.option_window) self.plot() if self.legend: self.make_legend_entry() def update_legend_picture(self): if self.legend: self.legend_pic.delete(self.tag) if self.line: self.legend_pic.create_line([(1,11), (20,11)], fill=self.line_color, smooth=self.line_smooth, width=self.line_width, tags=self.tag, dash=self.get_dash()) if self.style: self.marker_function(11, 11, canvas=self.legend_pic) self.legend_pic.pack(side=Tk.LEFT, anchor=Tk.W) def make_legend_entry(self): self.legend_entry = Tk.Frame(self.chart.legend, background=self.chart.background) self.legend_pic = Tk.Canvas(self.legend_entry, width=20, height=20, background=self.chart.background, bd=0, highlightbackground=self.chart.background) self.legend_text = Tk.Label(self.legend_entry, text=self.name, background=self.chart.background) self.legend_entry.bind('', self.chart.start_movement) self.legend_pic.bind('', self.chart.start_movement) self.legend_text.bind('', self.chart.start_movement) self.legend_entry.bind('', self.chart.legend_moving) self.legend_pic.bind('', self.chart.legend_moving) self.legend_text.bind('', self.chart.legend_moving) self.legend_entry.bind('', self.chart.stop_movement) self.legend_pic.bind('', self.chart.stop_movement) self.legend_text.bind('', self.chart.stop_movement) self.update_legend_picture() self.legend_text.pack(side=Tk.RIGHT, anchor=Tk.W) self.legend_entry.pack(side=Tk.TOP, anchor=Tk.W) def assign_marker_function(self): if self.style == 'circle': self.marker_function = self.make_circle elif self.style == 'diamond': self.marker_function = self.make_diamond elif self.style == 'triangle': self.marker_function = self.make_triangle elif self.style == 'square': self.marker_function = self.make_square else: self.marker_function = None def remove(self): self.chart.delete(self.tag) try: self.legend_entry.destroy() except AttributeError: pass def plot(self): self.chart.delete(self.tag) self.get_canvas_points() self.plot_line() self.plot_points() def get_dash(self): if self.line_dash: dash_value = (16, 8) else: dash_value = None return dash_value def option_window(self, event): class ColorOption(Tk.Frame): def __init__(self, text, color): Tk.Frame.__init__(self, color_frame) self.color = color self.button = Tk.Button(self, text=text, command=self.get_color) self.patch = Tk.Canvas(self, width=10, height=10, background=self.color) self.button.pack(side=Tk.TOP) self.patch.pack(side=Tk.BOTTOM) self.pack(side=Tk.LEFT, padx=4) def get_color(self): color_tuple, tkcolor = tkcc.askcolor(self.color) if tkcolor: self.color = tkcolor self.patch.configure(background=self.color) def apply_options(): self.style = self.style_list[style_rbs.get()] self.assign_marker_function() self.size = size_entry.get() self.border = border_color.color self.fill = fill_color.color self.line_color = line_color_option.color self.line = bool(use_line.get()) self.line_smooth = bool(smooth_line.get()) self.line_dash = bool(dashed_line.get()) self.line_width = width_line.get() self.plot() self.update_legend_picture() ow = modal_window(self.chart.master, title='Plot Series Options', buttons=True, user_accept=apply_options) style_frame = ow.append(Tk.Frame(ow)) # Point Style Frame # pstyle_frame = Tk.LabelFrame(style_frame, text='Point Style') styles = self.style_list[:4] + ['None'] styles = ['Square', 'Circle', 'Triangle', 'Diamond', 'None'] style_rbs = EasyRadiobuttons(style_frame, styles, text='Point Style', init=self.style_list.index(self.style)) size_entry = super_entry(style_rbs, label_text='Size', width=self.size, vartype='nonnegint', varvalue=self.size) size_entry.pack() style_rbs.pack(side=Tk.LEFT) # A little padding blank_label = Tk.Label(style_frame, width=3) blank_label.pack(side=Tk.LEFT) # Line Style Frame lstyle_frame = Tk.LabelFrame(style_frame, text='Line Style') use_line = EasyCheckbutton(lstyle_frame, text='Use a line', init=int(self.line)) smooth_line = EasyCheckbutton(lstyle_frame, text='Smooth line', init=int(self.line_smooth)) dashed_line = EasyCheckbutton(lstyle_frame, text='Dashed line', init=int(self.line_dash)) width_line = super_entry(lstyle_frame, label_text='Width', width=self.line_width, vartype='nonnegint', varvalue=self.line_width) use_line.pack(anchor=Tk.W) smooth_line.pack(anchor=Tk.W) dashed_line.pack(anchor=Tk.W) width_line.pack() lstyle_frame.pack(side=Tk.RIGHT, anchor=Tk.N) # Color Frame color_frame = ow.append(Tk.LabelFrame(ow, text='Colors')) border_color = ColorOption('Border', self.border) fill_color = ColorOption('Fill', self.fill) line_color_option = ColorOption('Line', self.line_color) ow.pack_and_display() def get_canvas_points(self): self.canvas_points = [ (self.chart.x_pixels(x), self.chart.y_pixels(y)) for x, y in self.points ] def plot_line(self, canvas=None): if self.line: linepoints = self.canvas_points if len(self.canvas_points) == 1: linepoints = linepoints * 2 if not canvas: self.chart.create_line(linepoints, fill=self.line_color, smooth=self.line_smooth, width=self.line_width, tags=self.tag, dash=self.get_dash()) else: canvas.create_line(linepoints, fill=self.line_color, smooth=self.line_smooth, width=self.line_width, tags=self.tag, dash=self.get_dash()) def plot_points(self): # Plots the individual points on the chart with the appropriate marker style if self.style: for point in self.canvas_points: self.marker_function(point[0], point[1]) def make_square(self, x, y, canvas=None): if not canvas: self.chart.create_rectangle((x-self.size, y-self.size), (x+self.size+1, y+self.size+1), fill=self.fill, outline=self.border, tags=self.tag) else: canvas.create_rectangle((x-self.size, y-self.size), (x+self.size+1, y+self.size+1), fill=self.fill, outline=self.border, tags=self.tag) def make_circle(self, x, y, canvas=None): if not canvas: self.chart.create_oval((x-self.size, y-self.size), (x+self.size+1, y+self.size+1), fill=self.fill, outline=self.border, tags=self.tag) else: canvas.create_oval((x-self.size, y-self.size), (x+self.size+1, y+self.size+1), fill=self.fill, outline=self.border, tags=self.tag) def make_diamond(self, x, y, canvas=None): delta = int(self.size * 1.4142) + 1 if not canvas: self.chart.create_polygon((x, y-delta), (x-delta, y), (x, y+delta), (x+delta, y), fill=self.fill, outline=self.border, tags=self.tag) else: canvas.create_polygon((x, y-delta), (x-delta, y), (x, y+delta), (x+delta, y), fill=self.fill, outline=self.border, tags=self.tag) def make_triangle(self, x, y, canvas=None): # Make the point the centroid of the triangle. The distance from the centroid to any vertex is 'size*1.1'. # Then the length of any side of the triangle is size*(sin(120)/sin(30)) = size*sqrt(3) = size*1.732 # The x-axis span of the bottom of the triangle is 1/2 the length of a side. # The y distance from the centroid to the bottom of the triangle is then sqrt(size^2 - (0.5*side)^2) tsize = self.size * 1.1 * 1.5 half_side = 0.5*tsize*1.732 ycb = math.sqrt(tsize*tsize - (half_side**2)) if not canvas: self.chart.create_polygon((x, y-tsize), (x-half_side, y+ycb), (x+half_side, y+ycb), fill=self.fill, outline=self.border, tags=self.tag) else: canvas.create_polygon((x, y-tsize), (x-half_side, y+ycb), (x+half_side, y+ycb), fill=self.fill, outline=self.border, tags=self.tag) class GenericPlot(EasyCanvas): # # A class that produces an X, Y scatter or line plot of data # Features: # Add X, Y series using add_series, add_line_points, add_line_slope_intercept # All plot series are fully customizable (point shapes, sizes, colors, lines, etc.) # User can customize all series and axis by clicking on them # Customize gridlines, # of ticks, labels, axis spans, etc. # Axis can autoadjust to fit the plotted data # Legend or no legend (legend can be moved) # Coordinates can be displayed for current mouse position # def __init__(self, master, height=130, width=201, background='white', xstart=0, xend=1000, ystart=0, yend=1000, font=None, xtitle=None, ytitle=None, xmajor=5, ymajor=5, xminor=3, yminor=3, ticklen=6, usex=True, usey=True, xgridmajor=False, xgridminor=False, ygridmajor=False, ygridminor=False, xdigits=0, ydigits=0, show_coords=False, allgridlines=False, uselegend=True, gridlinecolor='grey', xgridlinecolor=None, ygridlinecolor=None): # # height, width = size of the plot in pixels # background, gridlinecolor = color of the plot background and gridlines # xstart, xend = beginning and ending values for the x-axis in arbitrary units # ystart, yend = beginning and ending values for the y-axis in arbitrary units # font = font for the axes labels and titles # xtitle, ytitle = titles for the axes # xmajor, ymajor = number of major tickmarks for the x and y axes # xminor, yminor = number of minor tickmarks between each major tickmark for the x and y axes # ticklen = length of major tickmarks in pixels # usex, usey = True if we are using that axis # xgridmajor, xgridminor, ygridmajor, ygridminor = whether to use the specified gridlines # allgridlines = True if all gridlines should be used # xdigits, ydigits = number of digits past the decimal point for axis labels # show_coords = binds the mouse-over event to show coordinates in the upper left corner when the mouse passes over a curve # uselegend = True if using a legend # if background == 'system': background = 'SystemButtonFace' EasyCanvas.__init__(self, master, height=height, width=width, background=background) gp.params_to_attributes(self, ['master', 'height', 'width', 'xstart', 'xend', 'ystart', 'yend', 'font', 'xtitle', 'ytitle', 'xmajor', 'ymajor', 'xminor', 'yminor', 'ticklen', 'usex', 'usey', 'xgridmajor', 'xgridminor', 'ygridmajor', 'ygridminor', 'xdigits', 'ydigits', 'show_coords', 'background', 'uselegend', 'xgridlinecolor', 'ygridlinecolor'], locals()) if not self.font: self.font = tkFont.Font(family="helvetica", size=8) if allgridlines: self.xgridmajor = self.xgridminor = self.ygridmajor = self.ygridminor = True if gridlinecolor and not self.xgridlinecolor: self.xgridlinecolor = gridlinecolor if gridlinecolor and not self.ygridlinecolor: self.ygridlinecolor = gridlinecolor # Figure out how far from the bottom the x-axis starts self.y_high_low() # Now do the same for the y-axis self.x_left_right() # Draw the axes self.draw_x_axis() self.draw_y_axis() # Put the axis titles on self.axis_titles() if self.show_coords: self.tag_bind('curve', "", self.display_coords) self.tag_bind('curve', "", self.display_coords) # self.collections keeps the canvas tags for each set of data plotted on the chart self.collections = {} self.num_collections = 0 self.create_legend() self.legendx = self.width-1 self.legendy = 0 self.moving = False self.legend_window = self.create_window(self.legendx, self.legendy, tags='legend', anchor=Tk.NE) self.color_grabber = self.color_cycle() self.shape_grabber = self.shape_cycle() if self.uselegend: self.show_legend() def shape_cycle(self): shape_list = ['square', 'circle', 'triangle', 'diamond'] shape_count = 0 while True: try: shape = shape_list[shape_count] except IndexError: shape_count = 0 shape = shape_list[shape_count] shape_count = shape_count + 1 yield shape def color_cycle(self): color_list = ['black', 'red', 'green', 'yellow', 'purple', 'orange', 'sea green', 'cyan', 'pink', 'blue', 'white', 'brown'] def next_color(border_count, fill_count): try: # Grab the next color in the list border = color_list[border_count] except IndexError: # If we're at the end of the list, start over, and change the fill color border_count = 0 fill_count = fill_count + 1 border = color_list[border_count] if fill_count == -1: fill = color_list[border_count] else: try: # Get the fill color. Only have the border and the fill the same color if they are black if fill_count == border_count: fill = color_list[fill_count + 1] else: fill = color_list[fill_count] except IndexError: fill_count = 0 fill = color_list[fill_count] border_count = border_count + 1 return border, fill, border_count, fill_count fill_count = -1 border_count = 0 while True: border, color, border_count, fill_count = next_color(border_count, fill_count ) while cr.color_distance(self, self.background, border) < 0.1: border, color, border_count, fill_count = next_color(border_count, fill_count ) yield border, color def create_legend(self): self.legend = Tk.Frame(self.master, relief='ridge', bd=3, background=self.background) self.legend.bind('', self.start_movement) self.legend.bind('', self.legend_moving) self.legend.bind('', self.stop_movement) def start_movement(self, event): self.moving = True # w, h, x1, y1 = geostring_to_vals(self.winfo_toplevel().geometry()) x2, y2 = self.winfo_pointerxy() self.x_root = x2 self.y_root = y2 self.startx, self.starty = self.coords('legend') def stop_movement(self, event): self.moving = False def legend_moving(self, event): if self.moving: self.legendx = self.startx + (event.x_root - self.x_root) self.legendy = self.starty + (event.y_root - self.y_root) self.coords('legend', self.legendx, self.legendy) def show_legend(self): self.itemconfigure(self.legend_window, window=self.legend) def hide_legend(self): self.itemconfigure(self.legend_window, window=None) def y_high_low(self): # Figure out how far from the bottom the x-axis starts self.ylow = self.height self.yhigh = 1 + self.font.metrics('linespace')/ 2 if self.usex: self.ylow = self.ylow - (self.ticklen + self.font.metrics('ascent') + 1) if self.xtitle: self.ylow = self.ylow - (self.font.metrics('linespace') - 2) def x_left_right(self): # Figure out where the x-axis runs from left to right self.xleft = 2 if not self.usey: self.xleft = self.xleft + self.font.measure(gp.strfl(self.xstart, self.xdigits)) / 2 self.xright = self.width + 1 - (self.font.measure(gp.strfl(self.xend, self.xdigits)) / 2) if self.usey: self.xleft = self.xleft + (self.ticklen + max(self.font.measure(gp.strfl(self.ystart, self.ydigits)), self.font.measure(gp.strfl(self.yend, self.ydigits)))) + 2 if self.ytitle: self.ytitle_width = max([ self.font.measure(x) for x in self.ytitle ]) self.xleft = self.xleft + self.ytitle_width + 2 def axis_titles(self): self.delete('axis_title') if self.xtitle: xcoord = self.xleft + (self.xright - self.xleft)/2 ycoord = self.height - (self.font.metrics('linespace')/2) + 1 self.create_text(xcoord, ycoord, tags=('axis_title'), text=self.xtitle, font=self.font) if self.ytitle: xcoord = 5 + self.ytitle_width / 2 ycoord = self.yhigh + (self.ylow - self.yhigh)/2 self.create_text(xcoord, ycoord, tags=('axis_title'), text='\n'.join([x for x in self.ytitle]), font=self.font) def auto_scale_axes(self, margin=None, ymargin=0.2, xmargin=0.2): if margin: ymargin = margin xmargin = margin self.auto_scale_x_axis(margin=xmargin, replot=False) self.auto_scale_y_axis(margin=ymargin, replot=False) self.replot_series() def auto_scale_x_axis(self, margin=0.2, replot=True): xmax = max([ x.xmax for x in self.collections.values() ]) xmin = min([ x.xmin for x in self.collections.values() ]) xstart = xmin - margin*xmin xend = xmax + margin*xmax self.change_x_axis(xstart=xstart, xend=xend, replot=replot) def auto_scale_y_axis(self, margin=0.2, replot=True): ymax = max([ y.ymax for y in self.collections.values() ]) ymin = min([ y.ymin for y in self.collections.values() ]) ystart = ymin - margin*ymin yend = ymax + margin*ymax if ystart == yend: ystart = 0. yend = max(1.0, yend) self.change_y_axis(ystart=ystart, yend=yend, replot=replot) def replot_series(self): for series in self.collections.values(): series.plot() def change_x_axis(self, xstart=0, xend=1000, replot=True): self.xstart = xstart self.xend = xend self.delete('x-axis') self.clear() self.x_left_right() self.draw_x_axis() if replot: self.replot_series() def change_y_axis(self, ystart=0, yend=1000, replot=True): self.ystart = ystart self.yend = yend self.delete('y-axis') self.delete('x-axis') self.clear() self.delete('curve') self.x_left_right() self.draw_x_axis() self.draw_y_axis() if replot: self.replot_series() def major_interval(self, start, end, num): interval = abs(end - start) / (num - 1) if start > end: interval = -interval return interval def x_pixels(self, value): # Starting point + width_in_pixels * fraction return self.xleft + (self.xright - (1+self.xleft)) * float(value - self.xstart) / (self.xend - self.xstart) def y_pixels(self, value): return self.ylow - float(self.ylow - self.yhigh) * (value - self.ystart)/ (self.yend - self.ystart) def draw_x_axis(self): if not self.usex: return # The axis itself self.create_line([(self.xleft, self.ylow), (self.xright, self.ylow)], tags='x-axis') # Numerical labels, tickmarks and gridlines on the x-axis major_int = self.major_interval(self.xstart, self.xend, self.xmajor) xcoord = self.xstart try: minor_int = float(major_int)/(self.xminor + 1) except ZeroDivisionError: minor_int = 0 for label in range(self.xmajor+1): xpix = self.x_pixels(xcoord) # Major tickmark self.create_line((xpix, self.ylow), (xpix, self.ylow+self.ticklen-1), tags='x-axis') # Label self.create_text(xpix, self.ylow+self.ticklen-1, anchor=Tk.N, tags=('x-axis', 'xlabels'), text=gp.strfl(xcoord, self.xdigits), font=self.font) # Major gridline if self.xgridmajor and label != 0: self.create_line((xpix, self.ylow-1), (xpix, self.yhigh), tags=('x-axis', 'xgridmajor'), fill=self.xgridlinecolor) # Minor tickmarks if label+1 < self.xmajor: for tick in range(self.xminor): minor_pix = self.x_pixels(xcoord + (tick + 1) * minor_int) self.create_line((minor_pix, self.ylow), (minor_pix, self.ylow+self.ticklen-4), tags='x-axis') # Minor gridline if self.xgridminor: self.create_line((minor_pix, self.ylow-1), (minor_pix, self.yhigh), tags=('x-axis', 'xgridminor'), fill=self.xgridlinecolor) xcoord = xcoord + major_int self.tag_bind('x-axis', '', self.x_axis_options) def x_axis_options(self, *event): def change_color(): color_tuple, tkcolor = tkcc.askcolor(color_button.cget('bg')) if tkcolor: color_button.config(bg=tkcolor) def get_options(): try: xstart = minval.get() xend = maxval.get() self.xmajor = majors.get() self.xminor = minors.get() self.xgridmajor = gridmajors.get() self.xgridminor = gridminors.get() self.xdigits = digits.get() self.xgridlinecolor = color_button.cget('bg') self.change_x_axis(xstart=xstart, xend=xend) self.xtitle = title.get() self.axis_titles() except ValueError: pass opt_win = ModalWindow(self.master, title='X-axis Options', buttons=True, user_accept=get_options) minval = opt_win.append(SuperEntry(opt_win, label_text='Left Value', vartype='float', varvalue=self.xstart, width=10), anchor=Tk.W) maxval = opt_win.append(SuperEntry(opt_win, label_text='Right Value', vartype='float', varvalue=self.xend, width=10), anchor=Tk.W) digits = opt_win.append(SuperEntry(opt_win, label_text='Decimal places', vartype='int', varvalue=self.xdigits, width=2), anchor=Tk.W) title = opt_win.append(SuperEntry(opt_win, label_text='Title', varvalue=self.xtitle, width=25), anchor=Tk.W) majors = opt_win.append(SuperEntry(opt_win, label_text='Major Tickmarks', vartype='int', varvalue=self.xmajor, width=3), anchor=Tk.W) minors = opt_win.append(SuperEntry(opt_win, label_text='Minor Tickmarks', vartype='int', varvalue=self.xminor, width=3), anchor=Tk.W) gridmajors = opt_win.append(EasyCheckbutton(opt_win, init=self.xgridmajor, text='Major Gridlines'), anchor=Tk.W) gridminors = opt_win.append(EasyCheckbutton(opt_win, init=self.xgridminor, text='Minor Gridlines'), anchor=Tk.W) grid_color_frame = Tk.LabelFrame(opt_win, text='Gridline Color') color_button = Tk.Button(grid_color_frame, text=' ', bg=self.xgridlinecolor, command=change_color) color_button.pack() opt_win.append(grid_color_frame, anchor=Tk.W) opt_win.pack_and_display() def draw_y_axis(self): if not self.usey: return # The axis itself self.create_line([(self.xleft, self.ylow), (self.xleft, self.yhigh)], tags='y-axis') # Numerical labels, tickmarks and gridlines on the x-axis major_int = self.major_interval(self.ystart, self.yend, self.ymajor) ycoord = self.ystart try: minor_int = major_int/(self.yminor + 1) except ZeroDivisionError: minor_int = 0 for label in range(self.ymajor+1): ypix = self.y_pixels(ycoord) # Major tickmark self.create_line((self.xleft, ypix), (self.xleft-(self.ticklen-1), ypix), tags='y-axis') # Label self.create_text(self.xleft-(self.ticklen), ypix, anchor=Tk.E, tags=('y-axis', 'ylabels'), text=gp.strfl(ycoord, self.ydigits), font=self.font) # Major gridline if self.ygridmajor and label != 0: self.create_line((self.xleft+1, ypix), (self.xright, ypix), tags=('y-axis', 'ygridmajor'), fill=self.ygridlinecolor) # Minor tickmarks if label+1 < self.ymajor: for tick in range(self.yminor): minor_pix = self.y_pixels(ycoord + (tick + 1) * minor_int) self.create_line((self.xleft, minor_pix), (self.xleft - (self.ticklen-4), minor_pix), tags='y-axis') # Minor gridline if self.ygridminor: self.create_line((self.xleft+1, minor_pix), (self.xright, minor_pix), tags=('y-axis', 'ygridminor'), fill=self.ygridlinecolor) ycoord = ycoord + major_int self.tag_bind('y-axis', '', self.y_axis_options) def y_axis_options(self, *event): def change_color(): color_tuple, tkcolor = tkcc.askcolor(color_button.cget('bg')) if tkcolor: color_button.config(bg=tkcolor) def get_options(): try: ystart = minval.get() yend = maxval.get() self.ymajor = majors.get() self.yminor = minors.get() self.ygridmajor = gridmajors.get() self.ygridminor = gridminors.get() self.ydigits = digits.get() self.ygridlinecolor = color_button.cget('bg') self.change_y_axis(ystart=ystart, yend=yend) self.ytitle = title.get() self.axis_titles() except ValueError: pass opt_win = ModalWindow(self.master, title='Y-axis Options', buttons=True, user_accept=get_options) minval = opt_win.append(SuperEntry(opt_win, label_text='Bottom Value', vartype='float', varvalue=self.ystart, width=10), anchor=Tk.W) maxval = opt_win.append(SuperEntry(opt_win, label_text='Top Value', vartype='float', varvalue=self.yend, width=10), anchor=Tk.W) title = opt_win.append(SuperEntry(opt_win, label_text='Title', varvalue=self.ytitle, width=25), anchor=Tk.W) majors = opt_win.append(SuperEntry(opt_win, label_text='Major Tickmarks', vartype='int', varvalue=self.ymajor, width=3), anchor=Tk.W) minors = opt_win.append(SuperEntry(opt_win, label_text='Minor Tickmarks', vartype='int', varvalue=self.yminor, width=3), anchor=Tk.W) digits = opt_win.append(SuperEntry(opt_win, label_text='Decimal places', vartype='int', varvalue=self.ydigits, width=2), anchor=Tk.W) gridmajors = opt_win.append(EasyCheckbutton(opt_win, init=self.ygridmajor, text='Major Gridlines'), anchor=Tk.W) gridminors = opt_win.append(EasyCheckbutton(opt_win, init=self.ygridminor, text='Minor Gridlines'), anchor=Tk.W) grid_color_frame = Tk.LabelFrame(opt_win, text='Gridline Color') color_button = Tk.Button(grid_color_frame, text=' ', bg=self.ygridlinecolor, command=change_color) color_button.pack() opt_win.append(grid_color_frame, anchor=Tk.W) opt_win.pack_and_display() def display_coords(self, event): self.delete('coords') # Make sure we do floating point arithmatic, not integer! xpoint = float(event.x) ypoint = float(event.y) # X0 + range of x in units * delta/range x in pixels x = self.xstart + (self.xend-self.xstart) * (xpoint - self.xleft) / (self.xright - self.xleft) # Y0 + range of y in units * delta/range x in pixels y = self.yend - (self.yend - self.ystart) * (ypoint - self.yhigh) / (self.ylow - self.yhigh) self.create_text(self.width-1, 4, anchor=Tk.NE, text=gp.joiner(' ', x, y, digits=2), tags='coords') def add_series_xy(self, xvals, yvals, name='Generic', **keyw): # xvals and yvals are lists of x and y coordinates respectively # The added series is available to the user as a canvas objects with tag=name self.add_series(zip(xvals, yvals), name=name, **keyw) def add_series(self, points, name='Generic', **keyw): # See class PlotSeries for potential arguments # points is a list of (x, y) tuples # The added series is available to the user as a canvas objects with tag=name if keyw.get('style', None) and ('border' not in keyw or 'fill' not in keyw): # Set the default border and fill colors if the user did not specify them, and if we are using symbols border, fill = self.color_grabber.next() if 'border' not in keyw: keyw['border'] = border if 'fill' not in keyw: keyw['fill'] = fill elif not keyw.get('style', None): if 'line_color' not in keyw: if 'border' not in keyw or 'fill' not in keyw: border, fill = self.color_grabber.next() if 'border' not in keyw: keyw['border'] = border if 'fill' not in keyw: keyw['fill'] = fill else: # Set the border and fill color to the line color if it is specified if 'border' not in keyw: keyw['border'] = keyw['line_color'] if 'fill' not in keyw: keyw['fill'] = keyw['line_color'] if 'style' not in keyw: # Set the default shape keyw['style'] = self.shape_grabber.next() if name == 'Generic': # Make a unique name name = name + str(self.num_collections) self.collections[name] = PlotSeries(self, points, name=name, **keyw) self.num_collections = self.num_collections + 1 self.addtag_withtag('curve', self.collections[name]) def add_line_points(self, points, name='Generic', **keyw): # Add a line with a list of length 2 (x, y) tuples # The added line is available to the user as a canvas object with tag=name self.add_series(points, name=name, style=None, line=True, **keyw) def add_line_slope_intercept(self, slope=0, intercept=0, endpoints=None, name='Generic', **keyw): # Given a slope and intercept, add a line to the plot # If endpoints (x values) are not given, use the current endpoints on the plot # The added line is available to the user as a canvas object with tag=name if not endpoints: x1 = self.xstart x2 = self.xend else: x1 = endpoints[0] x2 = endpoints[1] y1 = x1*slope + intercept y2 = x2*slope + intercept points = [(x1, y1), (x2, y2)] self.add_line_points(points, name=name, **keyw) def remove_set(self, name): self.collections[name].remove() del self.collections[name] def reset(self): for series in self.collections.keys(): self.remove_set(series) self.clear() self.color_grabber = self.color_cycle() self.shape_grabber = self.shape_cycle() def clear(self): self.delete('curve') self.delete('coords') self.delete('legends') # This tag can be used by the user to add other stuff to the plot that gets cleared off self.delete('user_misc') class FilePicker(Tk.Frame): # # This class implements a file picker thingy that I've developed. The picker consists of: # A filename entry # An optional file extension entry # A listbox that is filled with filenames # # As the user types part of a filename in the filename entry, the listbox is updated to contain only filenames that match what # is in the filename entry box (and the optional extension entry) # # Use FilePicker.get_selections() to get a list of those filenames that are selected # Set doubleclick = to a function to execute when the user doubleclicks in the filelist # # paths = list of directory paths to check for files # width_file = width of filename input box # width_ext = width of extension input box # width_list = width of the filename listbox in characters # height_list = height of the filename listbox in lines # has_extension = True if an entry box should be created to allow the user to select the display extension (.###) # selectmode = Tk selection mode for the listbox # def_ext = default extension for files # def_file = default filename to put in the input box # has_extension = True if we should use the extension input box # ext_below = True if the extension input box should be below the filename input box if False, it is placed to the right # selectmode = selectmode for the filename listbox # buttons = True if the filename and extension input boxes should have buttons associated with them (really depricated at this point) # nohyphens = True if we should ignore hyphens in filenames when looking for a match # doubleclick = Set this to a function that should be called if the user doubleclicks on a list item. If False, nothing happens # def __init__(self, master, paths=[], width_file=20, width_ext=10, width_list=40, height_list=10, def_ext='.dat', def_file="", has_extension=True, ext_below=True, selectmode=Tk.EXTENDED, buttons=False, nohyphens=False, doubleclick=False): Tk.Frame.__init__(self, master) self.master = master self.filter_frame = Tk.Frame(self) self.paths = paths self.has_extension = has_extension self.ext_below = ext_below self.nohyphens = nohyphens # # Now make an file extension entry box and button if asked for # if self.has_extension: if buttons: self.extension = super_entry(self.filter_frame, button_text='Extension', command=self.update, width=width_ext, varvalue=def_ext, button_args={'pady':0}) else: self.extension = super_entry(self.filter_frame, label_text='Extension', width=width_ext, varvalue=def_ext, label_args={'pady':0}) # # Now make an entry box for the beginning of the file name # if buttons: self.beginning = super_entry(self.filter_frame, vartype='custom', button_text='Base Filename', command=self.update, width=width_file, customfilter=self.filter, varvalue=def_file, button_args={'pady':0}) else: self.beginning = super_entry(self.filter_frame, vartype='custom', label_text='Base Filename', width=width_file, customfilter=self.filter, varvalue=def_file, label_args={'pady':0}) # # Create the file listbox with scroll bar # self.list_frame = scroll_list(self, selectmode=selectmode, width=width_list, height=height_list) self.listbox = self.list_frame.listbox if doubleclick: self.listbox.bind('', self.doubleclick_event) self.doubleclick = doubleclick # # Load it up # self.dir_error = 0 self.make_list() self.update() self.pack_up() def doubleclick_event(self, event): # This routine gets called after a doubleclick on a listbox self.doubleclick() def get_selections(self): # Returns the list of filenames selected in the listbox selections = [ self.listbox.get(x) for x in self.listbox.curselection() ] return selections def filter(self, new): self.update(begin_string=new) return True def pack_up(self): # # Put the file listbox area into the GUI # self.beginning.grid(row=0, column=0, sticky=Tk.W) if self.has_extension: if self.ext_below: self.extension.grid(row=1, column=0, sticky=Tk.W) else: self.extension.grid(row=0, column=1, sticky=Tk.W) self.filter_frame.grid(row=0,sticky=Tk.W) self.list_frame.grid(row=1, sticky=Tk.NW+Tk.SE) def make_list(self): # # Gets the list of files for the listbox # self.filelist = [] self.dir_error = 0 for directory in self.paths: if (os.path.exists(directory)): self.filelist = self.filelist + dircache.listdir(directory) else: self.dir_error = 1 def delete_all(self): self.listbox.delete(0, Tk.END) def insert(self, item): self.listbox.insert(Tk.END, item) def update(self, begin_string=False): if self.nohyphens: # update_no_hyphens and update_with_hyphens are VERY similar, but are kept separate for purposes of speed (reducing # one 'if' statement per file) self.update_no_hyphens(begin_string) else: self.update_with_hyphens(begin_string) def update_with_hyphens(self, begin_string=False): if begin_string == False: begin_string = self.beginning.get() try: begin_string_low = begin_string.lower() except: begin_string_low = "" if self.has_extension: end_string = self.extension.get().lower() else: end_string = "" self.listbox.delete(0, Tk.END) for item in self.filelist: item_low = item.lower() # Only list the files with the proper extension and root name if begin_string == "" and end_string == "": self.listbox.insert(Tk.END, item) elif end_string == "": if item_low.startswith(begin_string_low): self.listbox.insert(Tk.END, item) elif begin_string == "": if item_low.endswith(end_string): self.listbox.insert(Tk.END, item) else: if item_low.endswith(end_string) and item_low.startswith(begin_string_low): self.listbox.insert(Tk.END, item) def update_no_hyphens(self, begin_string=False): if begin_string == False: begin_string = self.beginning.get() try: begin_string_low = begin_string.lower().replace('-','') except: begin_string_low = "" if self.has_extension: end_string = self.extension.get().lower().replace('-','') else: end_string = "" self.listbox.delete(0, Tk.END) for item in self.filelist: item_low = item.lower().replace('-','') # Only list the files with the proper extension and root name if begin_string == "" and end_string == "": self.listbox.insert(Tk.END, item) elif end_string == "": if item_low.startswith(begin_string_low): self.listbox.insert(Tk.END, item) elif begin_string == "": if item_low.endswith(end_string): self.listbox.insert(Tk.END, item) else: if item_low.endswith(end_string) and item_low.startswith(begin_string_low): self.listbox.insert(Tk.END, item) class file_picker(FilePicker): # Old name for the FilePicker class pass class ScrollList(Tk.Frame): # # Adds a Y scrollbar to a Tk.Listbox # def __init__(self, master, scrollside=Tk.LEFT, selectmode=Tk.BROWSE, width=20, height=10): Tk.Frame.__init__(self, master) self.master = master self.scrollbar = Tk.Scrollbar(self, orient=Tk.VERTICAL) self.listbox = Tk.Listbox(self, selectmode=selectmode, yscrollcommand=self.scrollbar.set, width=width, height=height) self.scrollbar.config(command=self.listbox.yview) self.scrollbar.pack(side=scrollside, fill=Tk.Y) self.listbox.pack(side=scrollside, fill=Tk.BOTH, expand=True) class scroll_list(ScrollList): # The old name of the ScrollList class pass class ScrollingText(Tk.Frame): # # Packs an EasyText instance into a frame with scrollbars # def __getattr__(self, attribute): # This is called if a method of ScrollingText is called that does not exist. If the method exists for the text widget, we # return that one instead. That is why in addition to calling text widget methods using self.text.method, you can also just # use self.method if hasattr(EasyText, attribute): # Must not reference self.text in the hasattr above, because __getattr__ is defined before __init__, so self.text # doesn't exist yet. But it will exist by the time any attributes are called for that EasyText has. return getattr(self.text, attribute) else: raise AttributeError def __init__(self, parent, text="", scrollx=True, scrolly=True, width=100, height=25, **kwarg): # Creates a frame that contains a text widget along with horizontal and vertical scrollbars # In addition to calling text widget methods using self.text.method, you can also just use self.method Tk.Frame.__init__(self, parent) if 'wrap' not in kwarg: kwarg['wrap'] = Tk.NONE self.text = EasyText(self, width=width, height=height, **kwarg) self.text.grid(row=0, column=0, sticky=Tk.E+Tk.W+Tk.N+Tk.S) if scrollx: self.scrollx = Tk.Scrollbar(self, orient=Tk.HORIZONTAL, command=self.text.xview) self.text.config(xscrollcommand=self.scrollx.set) self.scrollx.grid(row=height+1, column=0, sticky=Tk.E+Tk.W) if scrolly: self.scrolly = Tk.Scrollbar(self, orient=Tk.VERTICAL, command=self.text.yview) self.text.config(yscrollcommand=self.scrolly.set) self.scrolly.grid(row=0, column=1, rowspan=height, sticky=Tk.N+Tk.S) if text != "": self.text.insert(Tk.END, text) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) class Combobox(Tk.Frame): # This class forms a widget that has an entry at the top and a listbox below that is used to fill the entry def __getattr__(self, attribute): # This is called if a method of this class is called that does not exist. If the method exists for the child widget, we # return that one instead. That is why in addition to calling child widget methods using self.child.method, you can also just # use self.method if hasattr(Tk.Listbox, attribute): # Must not reference self.child in the hasattr above, because __getattr__ is defined before __init__, so self.child # doesn't exist yet. But it will exist by the time any attributes are called for that child has. return getattr(self.listbox, attribute) elif hasattr(Tk.Entry, attribute): return getattr(self.entry, attribute) else: raise AttributeError def __init__(self, parent, entry_args=None, user_edit=False, vartype=None, customfilter=None, popup=None, **kwarg): # # parent = the container for this widget # user_edit = True if the user is allowed to edit the entry box, False if not # vartype = type of data allowed in the entry box # customfilter = function that should be called if the entrybox is edited and vartype is set to "custom" # entry_args = Pass a dictionary of entry parameters with entry_args in order to pass those on to the entry. # popup = Pass a container here to have the listbox appear in that container (usually a Toplevel popup window), otherwise # the listbox appears in the same container as the entry # All non-specified paramters get passed to the Listbox through kwarg self.parent = parent Tk.Frame.__init__(self, parent) if popup: # popup should be set to whatever container the listbox should appear in, otherwise it shouldn't be set ezframe = popup else: ezframe = self self.ezlistbox = EasyListbox(ezframe, **kwarg) self.listbox = self.ezlistbox.listbox if not entry_args: entry_args = {} if 'width' not in entry_args: entry_args['width'] = int(self.listbox.cget('width')) if not user_edit: entry_args['state'] = Tk.DISABLED if 'bg' not in entry_args: entry_args['bg'] = self.listbox.cget('bg') if not user_edit: entry_args['disabledbackground'] = self.listbox.cget('bg') if 'fg' not in entry_args: entry_args['fg'] = self.listbox.cget('fg') if not user_edit: entry_args['disabledforeground'] = self.listbox.cget('fg') if vartype == 'int': self.entry = entry_int(self, **entry_args) elif vartype == 'float': self.entry = entry_float(self, **entry_args) elif vartype == 'nonnegfloat': self.entry = entry_nonnegfloat(self, **entry_args) elif vartype == 'nonnegint': self.entry = entry_nonnegint(self, **entry_args) elif vartype == 'custom': self.entry = entry_custom(self, filter=customfilter, **entry_args) else: self.entry = Tk.Entry(self, **entry_args) self.selection_command() self.listbox.bind('', self.selection_command) self.entry.pack(side=Tk.TOP, anchor=Tk.W, fill=Tk.X, expand=True) self.ezlistbox.pack(side=Tk.TOP, anchor=Tk.W, fill=Tk.X, expand=True) def get(self): return self.entry.get() def set(self, astring): if isinstance(astring, (list, tuple)): astring = gp.joiner(', ', astring) old_state = self.entry.cget('state') self.entry.configure(state=Tk.NORMAL) self.entry.delete(0, Tk.END) self.entry.insert(Tk.END, astring) self.entry.configure(state=old_state) def selection_command(self, *event): # Note that the Listbox supposedly has an event <> that could be used to get here - perhaps for future use? self.set(self.ezlistbox.get_values()) class DropDownCombobox(Combobox): # # An entry with an arrow button next to it. Clicking on the arrow button will create a popup listbox with options to fill the entry # with. # def __getattr__(self, attribute): # This is called if a method of this class is called that does not exist. If the method exists for the child widget, we # return that one instead. That is why in addition to calling child widget methods using self.child.method, you can also just # use self.method if hasattr(Tk.Listbox, attribute): # Must not reference self.child in the hasattr above, because __getattr__ is defined before __init__, so self.child # doesn't exist yet. But it will exist by the time any attributes are called for that child has. return getattr(self.listbox, attribute) elif hasattr(Tk.Entry, attribute): return getattr(self.entry, attribute) elif hasattr(Tk.Button, attribute): return getattr(self.button, attribute) else: raise AttributeError def __init__(self, parent, button_args=None, **kwarg): # parent = the container this widget appears in # Pass a dictionary of button parameters to button_args in order to pass those on to the button. # All other paramters get passed to the Combobox through kwarg - see Combobox class for options # Pass a dictionary of entry parameters to entry_args in order to pass those on to the entry. # # Methods called on a DropDownCombobox instance are searched for in the following order: # The DropDownCombobox, the ListBox, the Entry, the Button # The Listbox, entry and button can all be accessed via self.listbox, self.entry and self.button self.popup = Tk.Toplevel() self.popup.withdraw() Combobox.__init__(self, parent, popup=self.popup, **kwarg) self.popup.overrideredirect(True) self.button = Tk.Button(self, text='v', command=self.drop_it_down, pady=0, padx=0) self.entry.pack_forget() self.entry.pack(side=Tk.LEFT, anchor=Tk.N, fill=Tk.X, expand=True) self.button.pack(side=Tk.LEFT, anchor=Tk.N, pady=0, ipady=0, padx=0) def drop_it_down(self): if self.button.cget('text') == '^': return # Attach or remove the scrollbars as needed try: if self.listbox.size() > int(self.listbox.cget('height')): self.ezlistbox.scrolly.grid() else: self.ezlistbox.scrolly.grid_remove() except AttributeError: pass try: width = int(self.listbox.cget('width')) flagged = [ True for item in self.listbox.get(0, Tk.END) if len(str(item)) > width ] if flagged: self.ezlistbox.scrollx.grid() else: self.ezlistbox.scrollx.grid_remove() except AttributeError: pass # Figure out where to place the window entry_height = self.entry.winfo_height() entry_x = self.entry.winfo_rootx() entry_y = self.entry.winfo_rooty() mytop = entry_y + entry_height mygeo = gp.joiner('+', "", entry_x, mytop) # We may want to actually draw the window up from the entry rather than down if it extends past the bottom of the screen screen_bottom = self.winfo_screenheight() below_halfway = mytop > screen_bottom / 2 # do I start below the halfway point of the screen self.button.config(text='^') self.popup.geometry(mygeo) self.popup.deiconify() # Have to do the math after the window is visible, or else we get bogus geometry info myheight = self.popup.winfo_height() if mytop + myheight > screen_bottom and below_halfway: mytop = entry_y - myheight mygeo = gp.joiner('+', "", entry_x, mytop) self.popup.geometry(mygeo) self.popup.transient(self.parent) self.popup.focus_set() #self.monitor_dropdown() self.popup.bind('', self.rollup) self.time_delay_id = 0 self.popup.bind('', self.time_delay_rollup) self.popup.bind('', self.time_delay_cancel) self.popup.bind('', self.rollup) def time_delay_cancel(self, *event): if self.time_delay_id: self.after_cancel(self.time_delay_id) self.time_delay_id = 0 def time_delay_rollup(self, *event): # We want to roll the popup back up if the mouse leaves the popup area, but only if it leaves for good. We put in a time # delay here so we can cancel it if the user gets the mouse back in the popup area quickly # # The leave event seems to fire multiple times for each actual leave event, so only do this once per event if not self.time_delay_id: self.time_delay_id = self.after(1000, self.rollup) def rollup(self, *event): self.popup.withdraw() try: self.button.config(text='v') except Tk.TclError: pass class HelpWindow(Tk.Toplevel): def __init__(self, info='info', parent="", width=100, height=25, title='Help Window', icon=None, iconforce=False, disabled=True): # Creates a window that shows a block of text - makes it easy to give help in programs # Note - windows are not modal so they can be kept open # # info = text to put in window # parent = window to place this window on top of # width, height = the width (in characters) and height (in lines) of the text portion of the window # title = String to put in the titlebar of the window # icon = icon to show in the title bar # iconforce = normally, the icon is only used if on a windows operating system, use this if the icon should always by used # disabled = True if the text box should be disabled to further input # Tk.Toplevel.__init__(self, parent) # Note - self.window set to self for backwards compatibility self.window = self self.title(title) self.text_frame = ScrollingText(self, text=info, width=width, height=height) if disabled: self.text_frame.text.config(state=Tk.DISABLED) self.close = Tk.Button(self, text='Close', command=self.destroy) self.text_frame.pack(side=Tk.TOP, expand=True, fill=Tk.BOTH) self.close.pack(side=Tk.BOTTOM) if icon and (iconforce or sys.platform.startswith('win')): # This sets the little picture at the top left of the window bar # iconforce can be used to try to force the icon display even if this isn't a windows machine - it will likely fail self.iconbitmap(icon) self.text = self.text_frame.text class ModalWindow(Tk.Toplevel): # # Makes a toplevel window that is model (won't let you access the parent window when this window is open) # self.widget_list = List of widgets in the window # self.widget_show_args = list of arguments for packing those widgets # self.append() = method for adding widgets to the window # self.pack() = executes 'pack' on all the widgets in the window # self.grid() = executes 'grid' on all the widgets in the window # self.pack_and_display() = executes 'pack' on all the widgets in the window and displays the window # self.grid_and_display() = executes 'grid' on all the widgets in the window and displays the window # # parent = window to display on top of # icon = icon for the window manager to display on the title bar - only tried if a windows operating system unless iconforce=True # iconforce = True if we should try to display the icon even if we don't think the operating system will allow it # buttons = True to include a default set of "Accept" and "Cancel" buttons in the window # user_accept = the function that the "Accept" button should call before destroying itself # user_cancel = the function that the "Cancel" button should call before destroying itself # coords = determines where to place the upper left corner of the window # = None: window will be placed in the upper left corner of the parent window # = 'mouse': at the current mouse pointer coordinates with a little offset to put the bulk of the window under the mouse # = tuple or list: assumed to be (x, y) pixel coordinates relative to the screen # = if none of the above is true, coords is assumed to be a TK widget for the window to be placed in the upper left corner of # center = True if the center of the window should at coords instead of having the upper left corner at coords # center_of_widget = True if the window should be placed relative to the center of the widget if a widget is given for coords # *posargs and **kwargs are passed to Tk.Toplevel # # Widgets can be appended to the window using the append method. Then, when it is time to display the window, just use the # pack_and_display or grid_and_display method, which packs (or grids) the widgets for you. # def __init__(self, parent, title='modal window', icon=None, iconforce=False, buttons=False, user_accept=False, user_cancel=False, coords=None, center=True, center_of_widget=True, *posargs, **kwargs): Tk.Toplevel.__init__(self, *posargs, **kwargs) gp.params_to_attributes(self, ['icon', 'coords', 'buttons', 'user_accept', 'user_cancel', 'center', 'center_of_widget'], locals()) self.do_icon = icon != None and (iconforce or sys.platform.startswith('win')) self.parent_window = parent self.widget_list = [] self.widget_show_args = [] self.title(title) self.wm_protocol('WM_DELETE_WINDOW', self.cancel) def cancel(self): # The user should define a function and assign it to the modal window as follows: # bob = modal_window(parent, user_cancel=function) if self.user_cancel: self.user_cancel() self.destroy() def accept(self): # The user should define a function and assign it to the modal window as follows: # bob = modal_window(parent, user_accept=function) if self.user_accept: self.user_accept() self.destroy() def append(self, widget, **kwargs): # Adds the widget to our list, and returns it so the calling routine can use it easily self.widget_list.append(widget) self.widget_show_args.append(kwargs) return widget def display(self, grab=True): # Don't include the height and width in the geometry string or else the window doesn't automatically resize def make_geom_string(x, y): if self.center: x = max(0, x - self.winfo_width()/2) y = max(0, y - self.winfo_height()/2) return gp.joiner('+', "", x, y) def get_geo_from_parent(): # For a toplevel window, winfo_x, winfo_y give the coordinates of the upper-left pixel of the window # winfo_rootx, winfo_rooty give the coordinates of the upper-left Tk region of the window, # ie where the widgets go, ignoring the window title bar and border x = self.parent_window.winfo_x() y = self.parent_window.winfo_y() if self.center_of_widget: x = x + self.parent_window.winfo_width()/2 y = y + self.parent_window.winfo_height()/2 return x, y self.update_idletasks() if not self.coords: # Place in the upper left corner of the parent window my_x, my_y = get_geo_from_parent() elif isinstance(self.coords, (tuple, list)): # Place upper left corner of window at the given coordinates my_x, my_y = self.coords elif self.coords in ['mouse', 'MOUSE', 'Mouse']: # Place upper left corner of window at the mouse location coords = self.winfo_pointerxy() if coords != (-1, -1): if not self.center: # Put a little offset in to put the window more under the mouse my_x, my_y = (coords[0]-40, coords[1]-40) else: my_x, my_y = coords else: # Mouse is not locatable my_x, my_y = get_geo_from_parent() else: try: # Place at the upper left corner of the given widget # for a widget winfo_rootx,y are the pixel coordinates relative to the SCREEN! my_x, my_y = (self.coords.winfo_rootx(), self.coords.winfo_rooty()) if self.center_of_widget: my_x = my_x + self.coords.winfo_width()/2 my_y = my_y + self.coords.winfo_height()/2 except AttributeError: # Give up and place at the upper left corner of the parent window my_x, my_y = get_geo_from_parent() mygeo = make_geom_string(my_x, my_y) self.geometry(mygeo) # Transient makes this window not show up in the taskbar, and iconify, etc. with parent self.transient(self.parent_window) # focus_set makes this window active, which Windows doesn't always do without it self.focus_set() # The grab_set/wait_window combo means that this window stays on top and the parent # window doesn't respond to any mouseclicks if grab: # We may want to set grab=False if this window has widgets that generate another toplevel window # (such as a DropDownCombobox) self.grab_set() self.parent_window.wait_window(self) def pack(self): if self.buttons: self.gwacframe = self.append(Tk.Frame(self), fill=Tk.X) self.gwaccept_button = self.append(Tk.Button(self.gwacframe, text='Accept', command=self.accept), side=Tk.LEFT) self.gwcancel_button = self.append(Tk.Button(self.gwacframe, text='Cancel', command=self.cancel), side=Tk.RIGHT) for count in range(len(self.widget_list)): self.widget_list[count].pack(self.widget_show_args[count]) def grid(self): for count in range(len(self.widget_list)): self.widget_list[count].grid(self.widget_show_args[count]) if self.buttons: row, col = self.grid_size() self.gwacframe = Tk.Frame(self) self.gwaccept_button = Tk.Button(self.gwacframe, text='Accept', command=self.accept) self.gwcancel_button = Tk.Button(self.gwacframe, text='Cancel', command=self.cancel) self.gwacframe.grid(row=row, column=0, columnspan=col, sticky=Tk.W + Tk.E) self.gwaccept_button.pack(side=Tk.LEFT) self.gwcancel_button.pack(side=Tk.RIGHT) def pack_and_display(self, grab=True): self.pack() if self.do_icon: # This sets the little picture at the top left of the window bar self.iconbitmap(self.icon) self.display(grab=grab) def grid_and_display(self, grab=True): self.grid() if self.do_icon: # This sets the little picture at the top left of the window bar self.iconbitmap(self.icon) self.display(grab=grab) class modal_window(ModalWindow): # Dummy class with old name of ModalWindow pass class BusySignal(Tk.Toplevel): def __init__(self, parent, height=100, width=140, diameter=10, frames=30): # # Gives a bouncing ball in a box as a busy signal while a time-consuming 'thread' runs in another thread # Should be created once, then call i_am_busy() each time this should be displayed # The other thread MUST NOT make any calls to Tkinter objects - Tkinter is not thread safe! # # parent = window to display this in front of # height, width = height and width of the box (in pixels) # diameter = diameter of the ball # frames = number of frames per second - more frames = smoother ball movement # Tk.Toplevel.__init__(self, parent) self.colors = ['red', 'blue', 'green', 'yellow', 'purple', 'grey', 'gold', 'brown', 'black', 'dark green', 'orange', 'cyan'] self.overrideredirect(True) self.parent_window = parent self.parent_toplevel = self.parent_window.winfo_toplevel() self.width = width self.height = height self.diameter = diameter self.canvas = Tk.Canvas(self, height=self.height, width=self.width, bg='white') self.canvas.pack() self.center = (width/2, height/2) self.font = tkFont.Font(family="helvetica", size=32, weight='bold') self.text = self.canvas.create_text(self.center, fill='grey', text='BUSY', font=self.font) self.upleft = (self.center[0] - self.diameter, self.center[1] - self.diameter) self.ball = self.canvas.create_oval(self.ball_coords(), fill='red') self.step = 80 self.timestep = 1/float(frames) self.withdraw() def display(self): self.update_idletasks() mygeo = get_geometry(self) rootgeo = get_geometry(self.parent_toplevel) # move this window to the center of the application screen (geostring_to_vals returns (width, height, x, y)) left_edge = rootgeo[2] + rootgeo[0]/2 - mygeo[0]/2 top_edge = rootgeo[3] + rootgeo[1]/2 - mygeo[1]/2 set_geometry(self, mygeo[0], mygeo[1], left_edge, top_edge) # Transient makes this window not show up in the taskbar, and iconify, etc. with parent self.transient(self.parent_toplevel) # focus_set makes this window active, which Windows doesn't always do without it. We need to return the focus back to the # parent window when done, so save where the focus is now self.focus_widget = self.focus_get() self.focus_set() # The grab_set combo means that this window stays on top and the parent window doesn't respond to any mouseclicks self.grab_set() self.deiconify() def i_am_busy(self, function, *args, **kwargs): # # Use this method to display a busy box while function is running. # # function MUST NOT make any calls to Tkinter objects - Tkinter is not thread safe! # function = the function to run # args, kwargs = the arguments and keyword arguments to pass to function # self.canvas.itemconfigure(self.text, text='BUSY') self.upleft = self.center def get_move(): # Randomly determine the vector of the ball movement and roughly recenter the ball move = random.uniform(-self.step,self.step)*self.timestep if abs(move) < 1: move = move * 5 return move self.xmove = get_move() self.ymove = get_move() # Set up the thread for the time-consuming function # function = function to run in the other thread # args, kwargs = tuple of arguments and dictionary of keyword arguments respectively other_thread = threading.Thread(target=function, args=args, kwargs=kwargs) # Set other_thread to die if the program is killed other_thread.setDaemon(True) other_thread.start() self.display() while other_thread.isAlive(): time.sleep(self.timestep) self.move_ball() # This update (and not update_idletasks) is required or the screen doesn't update self.update() # Give focus back to the parent window if self.focus_widget: self.focus_widget.focus_set() else: self.parent_window.focus_set() self.grab_release() # Move the ball off screen and change the text self.canvas.coords(self.ball, (self.width+10, self.height+10, self.width+11, self.height+11)) self.canvas.itemconfigure(self.text, text='DONE!') self.update() self.withdraw() def ball_coords(self): return (self.upleft[0], self.upleft[1], self.upleft[0]+self.diameter, self.upleft[1]+self.diameter) def move_ball(self): self.upleft = (self.upleft[0]+self.xmove, self.upleft[1]+self.ymove) new_coords = self.ball_coords() if new_coords[0] < 0 or new_coords[2] > self.width: self.xmove = -self.xmove self.upleft = (self.upleft[0] + 2*self.xmove, self.upleft[1]) new_coords = self.ball_coords() self.canvas.itemconfigure(self.ball, fill=random.choice(self.colors)) if new_coords[1] < 0 or new_coords[3] > self.height: self.ymove = -self.ymove self.upleft = (self.upleft[0], self.upleft[1] + 2*self.ymove) new_coords = self.ball_coords() self.canvas.itemconfigure(self.ball, fill=random.choice(self.colors)) self.canvas.coords(self.ball, new_coords) class CustomEntry(Tk.Entry): def __init__(self, parent, validate_option='P', filter="", *posargs, **kwargs): # # This class creates an entry form that is restricted in a custom way defined by function 'filter'. The restriction # is enforced by the following scheme. I don't understand it all, but I got it off the internet and it works # A validation function is 'registered'. Registration creates a Tcl function from the python function # and that's part of the 'magic' I don't understand. But this allows one to pass in strings to # the function the way the validatecommand wants you to. # Next we tell the Entry that it should run a validation every time a key is pressed (validate='key') # The validatecommand option of the entry is set to the registered function plus a couple of strings. # In this case, %P is the new string after the key is pressed # Other options: %d = 0 for insertion, 1 for deletion and -1 for forced focus or validation (?) # %i = index of insertion/deletion (-1 if not insertion/deletion) # %S = text string being inserted/delete, ({} otherwise) # %v = the type of validation set (key, focusin, focusout, forced) # %V = the action that caused the validation # %W = the name of the entry widget # %s = the old string before the key was pressed # The validation function must return either true (validation is OK) or false. If it returns true, # the entry field accepts the edit. Otherwise it doesn't. # The form of the validatecommand option is validatecommand=registered_function + ' %X' # %X is one or more of the options above (space seperated) and the space MUST exist at the # beginning of the string # self.validate = parent.register(filter) Tk.Entry.__init__(self, parent, validate='key', validatecommand=self.validate+' %'+str(validate_option), *posargs, **kwargs) class entry_custom(CustomEntry): # The old name for the CustomEntry class pass class IntEntry(Tk.Entry): def __init__(self, parent, *posargs, **kwargs): # # This class creates an entry form that is restricted to integers (positive or negative). The restriction # is enforced by the following scheme. I don't understand it all, but I got it off the internet and it works # A validation function is 'registered'. Registration creates a Tcl function from the python function # and that's part of the 'magic' I don't understand. But this allows one to pass in strings to # the function the way the validatecommand wants you to. # Next we tell the Entry that it should run a validation every time a key is pressed (validate='key') # The validatecommand option of the entry is set to the registered function plus a couple of strings. # In this case, %P is the new string after the key is pressed # Other options: %d = 0 for insertion, 1 for deletion and -1 for forced focus or validation (?) # %i = index of insertion/deletion (-1 if not insertion/deletion) # %S = text string being inserted/delete, ({} otherwise) # %v = the type of validation set (key, focusin, focusout, forced) # %V = the action that caused the validation # %W = the name of the entry widget # %s = the old string before the key was pressed # The validation function must return either true (validation is OK) or false. If it returns true, # the entry field accepts the edit. Otherwise it doesn't. # The form of the validatecommand option is validatecommand=registered_function + ' %X' # %X is one or more of the options above (space seperated) and the space MUST exist at the # beginning of the string # self.validate = parent.register(self.filter) Tk.Entry.__init__(self, parent, validate='key', validatecommand=self.validate+' %P', *posargs, **kwargs) def filter(self, new): value = False try: test = int(new) value = True except: if new != "" and new != '-': value = False else: value = True return value class entry_int(IntEntry): # The old name for IntEntry pass class NonNegIntEntry(Tk.Entry): def __init__(self, parent, *posargs, **kwargs): # # This class creates an entry form that is restricted to positive integers. # See the intentry class definition for much more information. # self.validate = parent.register(self.filter) Tk.Entry.__init__(self, parent, validate='key', validatecommand=self.validate+' %P', *posargs, **kwargs) def filter(self, new): value = False try: test = int(new) if test >= 0: value = True else: value = False except: if new != "": value = False else: value = True return value class entry_nonnegint(NonNegIntEntry): # The old name for the NonNegIntEntry class pass class FloatEntry(Tk.Entry): def __init__(self, parent, *posargs, **kwargs): # # This class creates an entry form that is restricted to floating point values # See the intentry class definition for much more information. # self.validate = parent.register(self.filter) Tk.Entry.__init__(self, parent, validate='key', validatecommand=self.validate+' %P', *posargs, **kwargs) def filter(self, new): value = False try: test = float(new) value = True except: if new != "" and new != "-" and new != ".": value = False else: value = True return value class entry_float(FloatEntry): # The old name for the entry_float class pass class NonNegFloatEntry(Tk.Entry): def __init__(self, parent, *posargs, **kwargs): # # This class creates an entry form that is restricted to floating point values # See the intentry class definition for much more information. # self.validate = parent.register(self.filter) Tk.Entry.__init__(self, parent, validate='key', validatecommand=self.validate+' %P', *posargs, **kwargs) def filter(self, new): value = False try: test = float(new) value = True except: if new != "" and new != ".": value = False else: value = True return value class entry_nonnegfloat(NonNegFloatEntry): # The old name for the NonNegFloatEntry class pass class SuperEntry(Tk.Frame): # # Returns a frame that contains an entry form and a label and/or a button. Click on the button or hitting # in the entry form activates the passed in command. Both the label and the button are optional # # master = The widget this frame packs in # label_text = the text for the label widget - if None, then no label is created # label_args = dictionary of keyword arguments to pass to the Label widget on creation # label_pack_args = dictionary of the packing parameters for the labels (by default, to the left of the entry and anchored to the East # button_text = the text for the button widget - if None, then no button is created # button_args = dictionary of keyword arguments to pass to the Button widget on creation # button_pack_args = dictionary of the packing parameters for the labels (by default, to the left of the entry and anchored to the East # command = the command to run when the button is pressed or when the Return key is pressed # width = width of the entry in characters # entry_args = dictionary of keyword arguments to pass to the Entry widet on creation # entry_pack_args = dictionary of packing parameters for the entry (by default, to the right of the label/button and anchored West) # entry_text = Initial text to display in the entry widget # vartype = type of input the entry should accept. Options are 'int', 'float', 'nonnegfloat', 'nonnegint', and 'custom'. # varvalue = intial value of the data in the entry widget (overridden by entry_txt if that is given) # customfilter = the command to use as a custom filter if the entry is set to 'custom' # show = The character to show as the user types in the entry widget, if blank, the character typed is shown (use '*' for passwords) # # self.master = master widget for the fram # self.command = function that the button invokes # self.button = handle for the widget button # self.entry = handle for the widget button # self.label = handle for the widget button # self.var = the Tk variable for the entry # def __init__(self, master, label_text=None, label_pack_args={'side' : Tk.LEFT, 'anchor' : Tk.E}, button_text=None, command="", width=6, button_args={}, entry_args={}, vartype='string', varvalue="", button_pack_args={'side' : Tk.LEFT, 'anchor' : Tk.E}, entry_pack_args={'side' : Tk.RIGHT, 'anchor' : Tk.W}, customfilter=True, label_args={}, entry_text=None, show=""): Tk.Frame.__init__(self, master) self.master = master # # Create the entry # self.vartype = vartype if vartype == 'int': self.var = iVar(varvalue) self.entry = entry_int(self, entry_args, textvariable=self.var, width=width) elif vartype == 'float': self.var = dVar(varvalue) self.entry = entry_float(self, entry_args, textvariable=self.var, width=width) elif vartype == 'nonnegfloat': self.var = dVar(varvalue) self.entry = entry_nonnegfloat(self, entry_args, textvariable=self.var, width=width) elif vartype == 'nonnegint': self.var = iVar(varvalue) self.entry = entry_nonnegint(self, entry_args, textvariable=self.var, width=width) elif vartype == 'custom': self.var = sVar(varvalue) if len(entry_args) == 0: self.entry = entry_custom(self, filter=customfilter, textvariable=self.var, width=width) else: self.entry = entry_custom(self, entry_args, filter=customfilter, textvariable=self.var, width=width) else: self.var = sVar(varvalue) self.entry = Tk.Entry(self, entry_args, textvariable=self.var, width=width) if show: # will only show the "show" character instead of what is typed (use '*' for passwords, for instance) self.entry.config(show=show) if entry_text: self.set(entry_text) self.entry.config(entry_args) if command != "": self.entry.bind('', (lambda event : command())) if label_text != None: self.label = Tk.Label(self, text=label_text) self.label.config(label_args) if 'side' not in label_pack_args.keys(): label_pack_args['side'] = Tk.LEFT if 'anchor' not in label_pack_args.keys(): label_pack_args['anchor'] = Tk.E self.label.pack(label_pack_args) if button_text != None: self.button = Tk.Button(self, text=button_text, command=command) self.button.config(button_args) if 'side' not in button_pack_args.keys(): button_pack_args['side'] = Tk.LEFT if 'anchor' not in button_pack_args.keys(): button_pack_args['anchor'] = Tk.E self.button.pack(button_pack_args) if 'side' not in entry_pack_args.keys(): entry_pack_args['side'] = Tk.RIGHT if 'anchor' not in entry_pack_args.keys(): entry_pack_args['anchor'] = Tk.W self.entry.pack(entry_pack_args) def get(self): try: value = self.var.get() except ValueError: if self.vartype == 'float': value = 0.0 self.set(value) elif self.vartype == 'int': value = 0 self.set(value) else: value = "" self.set(value) return value def set(self, value): self.var.set(value) class super_entry(SuperEntry): # Dummy class with old name of SuperEntry pass # Some utility functions for dealing with text indicies def make_text_index(row, column): return '.'.join([str(row), str(column)]) def get_text_row(index): return int(index.split('.')[0]) def get_text_column(index): return int(index.split('.')[1]) # End of some utility functions for dealing with text indicies def get_all_my_widgets(widget): # Returns all the widgets contained in widget or (recursively) any of widget's descendents. The list is ordered so that children are # first, then grandchildren, etc. children = widget.winfo_children() count = 0 while count < len(children): children = children + children[count].winfo_children() count = count + 1 return children def destroy_all_my_widgets(widget): # Destroys all the widgets contained in widget or (recursively) any of widget's descendents. children = get_all_my_widgets(widget) for x in xrange(len(children)): widget = children.pop() widget.destroy() def params_to_tk_attributes(clobject, varlist, local_dict): # creates attributes on an class object (clobject) that are Tk variables. One attribute is created for each item in varlist, and # the name of the attribute is 'item' (item should be a string). The value of clobject.item is set to the value of 'item' in the # dictionary 'local_dict'. 'local_dict' should be the dictionary returned by calling 'locals()' from within clobject. # # The type of Tk variable used is determined by the Python type of the data it is set to. int and long are set to IntVar, # float are set to DoubleVar and anything else is set to StringVar # # Call from the __init__ function of a class object by: # params_to_attributes(class_object, variable_name_list, locals()) # class_object = the 'self' that is being __init__'d # variable_name_list = list of strings that are the names of the parameters to __init__ that are to be converted to # attributes of class_object # locals() = builtin python function that returns the local namespace as a dictionary # class testparm: # def __init__(self, bobby, joe='a', sue='ink'): # varlist = ['bobby', 'joe', 'sue'] # params_to_attributes(self, varlist, locals()) for key in varlist: value = local_dict[key] if isinstance(value, (int, long)): clobject.__dict__[key] = iVar(value) elif isinstance(value, float): clobject.__dict__[key] = dVar(value) else: clobject.__dict__[key] = sVar(value) # The routine below was commented out because the inspect module is not compatible with py2exe. #def params_to_tk_attrib2(*args, **keyw): ## Call from the __init__ function of a class object by: ## params_to_tk_attrib2(variables) ## variables = variables that are to be converted to Tk variables that are attributes of the class object ## if the class object refers to itself as a name other than 'self' (as in jimbo.variable to access the attribute ## 'variable' rather than self.variable), then that name should be supplied as a keyword parameter self='jimbo' ## Example: ## class testparm: ## def __init__(self, bobby, joe='a', sue='ink'): ## params_to_attrib2(bobby, joe, sue) ## atest = testparm(6) ## Yields: ## atest.bobby.get() = 6 (can be accessed from within the body of testparm as self.bobby) ## atest.joe.get() = 'a' (can be accessed from within the body of testparm as self.joe) ## atest.sue.get() = 'ink' (can be accessed from within the body of testparm as self.sue) ## ## This routine does the same thing as params_to_tk_attributes but has a simpler calling syntax # ## This gets stack information for the calling routine (stack()[0] is the stack information for this routine, etc.) #mycaller = inspect.stack()[1] # ## This gets the actual source code of the calling line so we can grab the variable names from it #calling_line = mycaller[4][0] # ## Now we parse out the variable names and clean up and spaces, etc. #index = calling_line.index('params_to_attrib2(') #arg_names_list = calling_line[index+18: calling_line.index(')', index)] #mynames = [] #for name in arg_names_list.split(','): #clean_name = name.strip() #if name.find('=') == -1: #mynames.append(clean_name) # ## mycaller[0] is the frame of the calling routine, f_locals is the dictionary of local variables for that frame #local_dict = mycaller[0].f_locals ## The class object itself is stored in the local_dict, usually under the name 'self', but the user can supply self='x' if the class ## object refers to itself as 'x' rather than 'self'. dict.get(x, y) returns y if x is not a key in the dictionary. #clobject = local_dict[keyw.get('self', 'self')] #for key in mynames: #value = local_dict[key] #if isinstance(value, (int, long)): #clobject.__dict__[key] = iVar(value) #elif isinstance(value, float): #clobject.__dict__[key] = dVar(value) #else: #clobject.__dict__[key] = sVar(value) def get_geometry(widget): # Returns width, height, x and y coordinates of widget as integers return geostring_to_vals(widget.winfo_geometry()) def set_geometry(widget, width, height, x, y): # Sets the geometry of widget using width, height, x and y as integers widget.wm_geometry(vals_to_geostring(width, height, x, y)) def geostring_to_vals(astring): # Converts a Tk geometry string of the form "wxh+-x+-y" to integer values width, height, x, y # this line takes care of strings of the form wxh-x-y astring = astring.replace('-', '+-') # And this line cleans up lines of the form wxh+-x+-y which the above line turns into wxh++-x++-y astring = astring.replace('++', '+') wh, x, y = astring.split('+') width, height = wh.split('x') x = int(x) y = int(y) width = int(width) height = int(height) return width, height, x, y def vals_to_geostring(width, height, x, y): # Converts width, height, x and y to a Tk geometry string of the form "wxh+-x+-y" astring = gp.joiner('x', width, height, digits=0) bstring = gp.joiner('+', astring, x, y, digits=0) bstring = bstring.replace('+-', '-') return bstring def valid_tag(astring): # Replaces the whitespace in a string with '_' to make a valid canvas tag tag = '_'.join(astring.split()) return tag #^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # # End of GUI widget definitions # #--------------------------------------------------------------------------------------------------- if __name__ == '__main__': def button_routine(): print 'Hello!' root = Tk.Tk() entry_args = { 'bg' : 'red' } frame = super_entry(root, button_text='Howdy!', command=button_routine, width=13, vartype='int', varvalue=14, entry_args=entry_args) frame.pack() class testparm: def __init__(self, bobby, joe=1, sue='ink'): varlist = ['bobby', 'joe', 'sue'] params_to_tk_attributes(self, varlist, locals()) print self.bobby.get() print self.joe.get() print self.sue.get() atest = testparm(0.05) root.mainloop()