#!/usr/bin/env python ################################################################################ # # This file is part of Gato (Graph Algorithm Toolbox) # You can find more information at # http://gato.sf.net # # file: EditObjectAttributesDialog.py # author: Alexander Schliep (schliep@molgen.mpg.de) # # Copyright (C) 1998-2005, Alexander Schliep, Winfried Hochstaettler and # Copyright 1998-2001 ZAIK/ZPR, Universitaet zu Koeln # # Contact: schliep@molgen.mpg.de, wh@zpr.uni-koeln.de # # Information: http://gato.sf.net # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Library General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Library General Public License for more details. # # You should have received a copy of the GNU Library General Public # License along with this library; if not, write to the Free # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # # # This file is version $Revision: 1.17 $ # from $Date: 2005/02/22 11:12:45 $ # last change by $Author: schliep $. # ################################################################################ from Tkinter import * from ScrolledText import * import tkSimpleDialog import tkMessageBox from tkColorChooser import askcolor import copy import sys import os import types def typed_assign(var, val): result = type(var)(val) result.__dict__ = copy.copy(var.__dict__) return result #------------------------------------------------------------------------------- class TkStringEntry: """Tk entry field for editing strings""" def __init__(self, master, width): self.entryWidget = Entry(master, width=width, exportselection=FALSE) def tkWidget(self): return self.entryWidget def get(self): return self.entryWidget.get() def set(self, value): self.entryWidget.delete(0,END) self.entryWidget.insert(0,"%s" % value) def select(self): self.entryWidget.selection_range(0,"end") self.entryWidget.focus_set() class TkIntEntry(TkStringEntry): """Tk entry field for editing one integer""" def get(self): return int(self.entryWidget.get()) class TkFloatEntry(TkStringEntry): """Tk entry field for editing one float""" def get(self): return float(self.entryWidget.get()) class TkDefaultMixin: """Mixin for TkStringEntry, TkIntEntry, TkFloatEntry, ... to deal with values which have an externally defined default value. Combination of 'use default' checkbox and corresponding entry field """ def __init__(self, master, useDefault, defaultValue): self.frame = Frame(master, relief=FLAT) self.useDefault = IntVar() self.useDefault.set(useDefault) self.defaultValue = defaultValue useDefaultButton = Checkbutton(self.frame, text="Use default", variable=self.useDefault, command=self.toggleDefault) useDefaultButton.grid(row=0, column=0, padx=4, pady=3, sticky=W) def finish(self): self.entryWidget.grid(row=0, column=1, padx=4, pady=3, sticky=W) self.switchDefault(self.useDefault.get()) def UseDefault(self): return self.useDefault.get() def switchDefault(self, value): if value == 0: self.entryWidget['state'] = NORMAL self.entryWidget.delete(0,END) self.set(self.defaultValue) else: self.entryWidget.delete(0,END) self.entryWidget['state'] = DISABLED def toggleDefault(self): self.switchDefault(self.useDefault.get()) class TkDefaultStringEntry(TkStringEntry, TkDefaultMixin): def __init__(self, master, width, useDefault, defaultValue): TkDefaultMixin.__init__(self, master, useDefault, defaultValue) TkStringEntry.__init__(self, self.frame, width) self.finish() def tkWidget(self): # To avoid ambiguity return self.frame class TkDefaultIntEntry(TkIntEntry, TkDefaultMixin): def __init__(self, master, width, useDefault, defaultValue): TkDefaultMixin.__init__(self, master, useDefault, defaultValue) TkIntEntry.__init__(self, self.frame, width) self.finish() def tkWidget(self): # To avoid ambiguity return self.frame def get(self): if self.UseDefault(): return self.defaultValue else: return TkIntEntry.get(self) class TkDefaultFloatEntry(TkFloatEntry, TkDefaultMixin): def __init__(self, master, width, useDefault, defaultValue): TkDefaultMixin.__init__(self, master, useDefault, defaultValue) TkFloatEntry.__init__(self, self.frame, width) self.finish() def tkWidget(self): # To avoid ambiguity return self.frame def get(self): if self.UseDefault(): return self.defaultValue else: return TkFloatEntry.get(self) class TkPopupSelector: def __init__(self, master, value2pop, pop2value, width): self.value2pop = value2pop self.pop2value = pop2value self.popupvalue = StringVar() self.popupvalue.set(self.pop2value.keys()[0]) # XXX first value as default # XXX Uuughhh keys = self.value2pop.keys() keys.sort() pops = map(lambda x: value2pop[x], keys) #log.debug("pops = %s" % pops) args = (master, self.popupvalue) + tuple(pops) self.tkwidget = apply(OptionMenu, args) self.tkwidget.config(height=1, width=width) def tkWidget(self): return self.tkwidget def get(self): return self.pop2value[self.popupvalue.get()] def set(self, value): try: self.popupvalue.set(self.value2pop[value]) except: self.popupvalue.set(self.pop2value.keys()[0]) # XXX first value as default def select(self): # Cant choose invalid value with popup pass class TkStringPopupSelector: def __init__(self, master, strings): self.strings = strings self.popupvalue = StringVar() if len(self.strings) > 0: self.popupvalue.set(self.strings[0]) # XXX first value as default width = max(map(len, self.strings)) args = (master, self.popupvalue) + tuple(self.strings) self.tkwidget = apply(OptionMenu, args) self.tkwidget.config(height=1, width=width) def tkWidget(self): return self.tkwidget def get(self): return self.popupvalue.get() def set(self, value): try: self.popupvalue.set(value) except: self.popupvalue.set(self.strings[0]) # XXX first value as default def select(self): # Cant choose invalid value with popup pass class TkColorSelector: def __init__(self, master, color='black'): #self.tkwidget = Button(master, width=8, command=self.editColor) self.tkwidget = Frame(master, height=18, width=60, relief=RIDGE, borderwidth=1) self.tkwidget.bind("", self.editColor) self.set(color) def editColor(self, event): color = askcolor(self.color)[1] if color is not None: self.set(color) def tkWidget(self): return self.tkwidget def get(self): return self.color def set(self, value): self.color = value self.tkwidget.config(bg=self.color) def select(self): # Cant choose invalid value with popup pass class EditObjectAttributesDialog(tkSimpleDialog.Dialog): """ Creates an editable (pseudo-)inspector for a selected set of attributes of a given object - master : tk master widget - object : the object, whose attributes we want to edit - attr_names : a list of attr_names By making use of Python 2.2's capability of subclassing built-in types such as ints, information about editing etc. is conveyed. An attr must have: - validate(value) method [return 1, if value is a valid new value for attr] The class of an attr can have the following mix-ins: - Popubable - WithDefault """ def __init__(self, master, object, attr_names): self.object = object self.attr_names = attr_names self.edit = {} tkSimpleDialog.Dialog.__init__(self, master, "Edit: %s" % self.object.desc) def editWidget(self, master, object, attr_name): """ Create a widget capable of editing attr and insert attr's current value""" attr = object.__dict__[attr_name] attr_type = type(attr) widget = None default = isinstance(attr, WithDefault) # has a WithDefault mixin if isinstance(attr, Popupable): widget = TkPopupSelector(master, attr.val2pop, attr.pop2val, attr.width) elif isinstance(attr, str): if default: widget = TkDefaultStringEntry(master, max(32, len(attr)), attr.useDefault, attr) else: widget = TkStringEntry(master, max(32, len(attr))) elif isinstance(attr, int): if default: widget = TkDefaultIntEntry(master, 6, attr.useDefault, attr) else: widget = TkIntEntry(master, 6) elif isinstance(attr, float): if default: widget = TkDefaultFloatEntry(master, 8, attr.useDefault, attr) else: widget = TkFloatEntry(master, 8) widget.set(attr) return widget def body(self, master): self.resizable(0,0) # Header Zeile label = Label(master, text="Name", anchor=E) label.grid(row=0, column=0, padx=4, pady=3, sticky=E) label = Label(master, text="Value", anchor=W) label.grid(row=0, column=1, padx=4, pady=3, sticky=W) cur_row = 1 for attr in self.attr_names: label = Label(master, text="%s" % attr, anchor=E) label.grid(row=cur_row, column=0, padx=4, pady=3, sticky=E) self.edit[attr] = self.editWidget(master, self.object, attr) if self.edit[attr] != None: self.edit[attr].tkWidget().grid(row=cur_row, column=1, padx=2, pady=1, sticky=W) cur_row = cur_row + 1 def validate(self): for attr_name in self.edit.keys(): try: # In python 2.2 we can subclass attributes and add a validate method # to attributes value = self.edit[attr_name].get() if self.object.__dict__[attr_name].validate(value) == 0: raise ValueError except ValueError: msg = "Please enter a valid value for %s" % attr_name tkMessageBox.showwarning("Invalid Value", msg, parent=self) self.edit[attr_name].select() return 0 # Everything is valid => set values for attr_name in self.edit.keys(): self.object.__dict__[attr_name] = typed_assign(self.object.__dict__[attr_name], self.edit[attr_name].get()) if isinstance(self.object.__dict__[attr_name], WithDefault): self.object.__dict__[attr_name].useDefault = self.edit[attr_name].useDefault.get() return 1 #------------------------------------------------------------------------------- class WithDefault: """Mix-in for variables which have a default value""" def setDefault(self, useDefault, defaultValue): self.useDefault = useDefault self.defaultValue = defaultValue def validate(self, value): ## if self.useDefault: ## return 1 ## else: ## return 1 # XXX How can I call a method of the class I am mixed too return 1 class Popupable: """Mix-in for variables which can be edited via a pop-up menu - val2pop : dict mapping value to string for pop up menu - pop2val: dict mapping pop up menu string to value - width: maximal string length in pop up """ def setPopup(self, val2pop, pop2val = None, width = None): self.val2pop = val2pop self.pop2val = None self.width = None if pop2val == None: self.pop2val = {} # Private copy self.width = 0 for val in val2pop.keys(): pop = val2pop[val] self.width = max(len(pop), self.width) self.pop2val[pop] = val else: self.pop2val = pop2val self.width = width def validate(self, value): return 1 ##class PopupableStr(str): ## """Class for variables which can be edited via a pop-up menu ## - values: array of values ## - width: maximal string length in pop up ## """ ## def setPopup(self, values, width = None): ## self.values = values ## self.width = width ## if width == None: ## self.width = 0 ## for s in values: ## self.width = max(len(s), self.width) ## def validate(self, value): ## return 1 class AlwaysValidate: """Mix-in for variables which always are valid""" def validate(self, value): return 1 #------------------------------------------------------------------------------- class ValidatingInt(int, AlwaysValidate): """Editable replacement for ints""" pass class ValidatingFloat(float, AlwaysValidate): """Editable replacement for floats""" pass class ValidatingString(str, AlwaysValidate): """Editable replacement for strings""" pass class PopupableInt(int, Popupable): """A replacement for ints editable via a pop-up""" pass class Probability(float): """An editable float taking values from [0,1]""" def validate(self, value): if 0.0 <= value and value <= 1.0: return 1 else: return 0 class DefaultedInt(int, WithDefault): """An editable int with a default value""" pass class DefaultedFloat(float, WithDefault): """An editable float with a default value""" pass class DefaultedString(str, WithDefault): """An editable strinf with a default value""" pass #====================================================================== # # Demo: # class TkTestFrame(Frame): def __init__(self, parent=None): Frame.__init__(self,parent) Pack.config(self) self.createWidgets() self.desc = ValidatingString("The TkTestFrame") self.x = DefaultedInt(1) self.x.setDefault(1, 122) self.y = ValidatingFloat(2.33) self.choose = PopupableInt(3) self.pop2val = {"aaa":1, "xxx":2, "sss":3} self.val2pop = {1:"aaa", 2:"xxx", 3:"sss"} self.choose.setPopup(self.val2pop, self.pop2val, 5) def createWidgets(self): self.QUIT = Button(self, text='QUIT', foreground='red', command=self.quit) self.QUIT.pack(side=LEFT) self.About = Button(self, text='Preferences', foreground='red', command=self.About) self.About.pack(side=LEFT) def About(self): aboutBox = EditObjectAttributesDialog(self.master, self, ['desc', 'x', 'y', 'choose']) del self.pop2val["aaa"] del self.val2pop[1] aboutBox = EditObjectAttributesDialog(self.master, self, ['desc', 'x', 'y', 'choose']) if __name__ == '__main__': app = TkTestFrame() app.mainloop()