"""Contains the WidgetPropertiesDialog class.""" __all__ = ['WidgetPropertiesDialog'] from direct.showbase.TkGlobal import * import Pmw, sys """ TODO: Checkboxes for None? Floaters to adjust float values OK and Cancel to allow changes to be delayed Something other than Return to accept a new value """ class WidgetPropertiesDialog(Toplevel): """Class to open dialogs to adjust widget properties.""" def __init__(self, propertyDict, propertyList = None, parent = None, title = 'Widget Properties'): """Initialize a dialog. Arguments: propertyDict -- a dictionary of properties to be edited parent -- a parent window (the application window) title -- the dialog title """ # Record property list self.propertyDict = propertyDict self.propertyList = propertyList if self.propertyList is None: self.propertyList = list(self.propertyDict.keys()) self.propertyList.sort() # Use default parent if none specified if not parent: if sys.version_info >= (3, 0): import tkinter parent = tkinter._default_root else: import Tkinter parent = Tkinter._default_root # Create toplevel window Toplevel.__init__(self, parent) self.transient(parent) # Set title if title: self.title(title) # Record parent self.parent = parent # Initialize modifications self.modifiedDict = {} # Create body body = Frame(self) self.initial_focus = self.body(body) body.pack(padx=5, pady=5) # Create OK Cancel button self.buttonbox() # Initialize window state self.grab_set() self.protocol("WM_DELETE_WINDOW", self.cancel) self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50)) self.initial_focus.focus_set() self.wait_window(self) def destroy(self): """Destroy the window""" self.propertyDict = {} self.initial_focus = None # Clean up balloons! for balloon in self.balloonList: balloon.withdraw() Toplevel.destroy(self) # # construction hooks def body(self, master): """create dialog body. return entry that should have initial focus. This method should be overridden, and is called by the __init__ method. """ count = 0 entryList = [] self.balloonList = [] for property in self.propertyList: propertySet = self.propertyDict[property] # Widget widget = propertySet.get('widget', None) # Get initial value initialvalue = widget[property] # Type of entry entryType = propertySet.get('type', 'real') # Is None an allowable value? fAllowNone = propertySet.get('fNone', 0) # Help string specified? helpString = propertySet.get('help', None) # Create label label = Label(master, text=property, justify=LEFT) label.grid(row=count, column = 0, padx=5, sticky=W) # Create entry entry = Pmw.EntryField(master, entry_justify = 'right') entry.grid(row=count, column = 1, padx=5, sticky=W+E) if initialvalue is None: entry.insert(0, 'None') else: entry.insert(0, initialvalue) # Create balloon for help balloon = Pmw.Balloon(state = 'balloon') self.balloonList.append(balloon) # extra info if None is allowed value if helpString is None: if fAllowNone: extra = ' or None' else: extra = '' # Set up help string and validator based upon type if entryType == 'real': # Only allow real numbers if fAllowNone: entry['validate'] = { 'validator': self.realOrNone } else: entry['validate'] = { 'validator': 'real' } if helpString is None: helpString = 'Enter a floating point number' + extra + '.' elif entryType == 'integer': # Only allow integer values if fAllowNone: entry['validate'] = { 'validator': self.intOrNone } else: entry['validate'] = { 'validator': 'integer' } if helpString is None: helpString = 'Enter an integer' + extra + '.' else: # Anything goes with a string widget if helpString is None: helpString = 'Enter a string' + extra + '.' # Bind balloon with help string to entry balloon.bind(entry, helpString) # Create callback to execute whenever a value is changed modifiedCallback = (lambda f=self.modified, w=widget, e=entry, p=property, t=entryType, fn=fAllowNone: f(w, e, p, t, fn)) entry['modifiedcommand'] = modifiedCallback # Keep track of the entrys entryList.append(entry) count += 1 # Set initial focus if len(entryList) > 0: entry = entryList[0] entry.select_range(0, END) # Set initial focus to first entry in the list return entryList[0] else: # Just set initial focus to self return self def modified(self, widget, entry, property, type, fNone): self.modifiedDict[property] = (widget, entry, type, fNone) def buttonbox(self): """add standard button box buttons. """ box = Frame(self) # Create buttons w = Button(box, text="OK", width=10, command=self.ok) w.pack(side=LEFT, padx=5, pady=5) # Create buttons w = Button(box, text="Cancel", width=10, command=self.cancel) w.pack(side=LEFT, padx=5, pady=5) # Bind commands self.bind("", self.ok) self.bind("", self.cancel) # Pack box.pack() def realOrNone(self, val): val = val.lower() if 'none'.find(val) != -1: if val == 'none': return Pmw.OK else: return Pmw.PARTIAL return Pmw.realvalidator(val) def intOrNone(self, val): val = val.lower() if 'none'.find(val) != -1: if val == 'none': return Pmw.OK else: return Pmw.PARTIAL return Pmw.integervalidator(val) # # standard button semantics def ok(self, event=None): self.withdraw() self.update_idletasks() self.validateChanges() self.apply() self.cancel() def cancel(self, event=None): # put focus back to the parent window self.parent.focus_set() self.destroy() def validateChanges(self): for property in self.modifiedDict: tuple = self.modifiedDict[property] widget = tuple[0] entry = tuple[1] type = tuple[2] fNone = tuple[3] value = entry.get() lValue = value.lower() if 'none'.find(lValue) != -1: if fNone and (lValue == 'none'): widget[property] = None else: if type == 'real': value = float(value) elif type == 'integer': value = int(value) widget[property] = value def apply(self): """process the data This method is called automatically to process the data, *after* the dialog is destroyed. By default, it does nothing. """ pass # override