""" Edit Boxes, Combobox, etc """ # Copyright (C) 2004 Henning Jacobs # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # $Id: InputWidgets.py 92 2004-11-28 15:34:44Z henning $ import sys, os from Tkinter import * import Pmw import debug import IconImages import ToolTip class ArrowButton(Canvas): def __init__(self, master, direction='down', command=None, **kws): kws.setdefault('width', 16) kws.setdefault('height', 16) kws.setdefault('borderwidth', 2) Canvas.__init__(self, master, relief=RAISED, highlightthickness=0, **kws) self._disabled = False self.direction = direction self.command = command self.bind('', self._btnPress) self.bind('', self._btnRelease) self._drawArrow() _arrowRelief = RAISED def _drawArrow(self, sunken=0): if sunken: self._arrowRelief = self.cget('relief') self.configure(relief = SUNKEN) else: self.configure(relief = self._arrowRelief) if self._disabled: color = '#aaaaaa' else: color = '#000000' Pmw.drawarrow(self, color, self.direction, 'arrow') def _btnPress(self, event): if not self._disabled: self._drawArrow(sunken=1) def _btnRelease(self, event): if not self._disabled: if self.command: self.command() self._drawArrow() def setdirection(self, dir): self.direction = dir self._drawArrow() def configure(self, **kws): if kws.has_key('state'): self._disabled = kws['state'] == 'disabled' self._drawArrow() Canvas.configure(self, **kws) class AbstractSingleVarEdit: def __init__(self): self._state = NORMAL self._var = None self._save_hooks = [] # Descendants should implement the following: #def get(self): # pass #def clear(self): # pass #def set(self): # pass def save(self, event=None): if self._state != DISABLED and self._var is not None: if self._var.get() != self.get(): self._var.set(self.get()) for hook in self._save_hooks: hook() def add_save_hook(self, save_hook): self._save_hooks.append(save_hook) def bindto(self, var): self.save() self._var = None self.clear() if var is not None: self._var = var self.set(var.get()) class LabelPseudoEdit(AbstractSingleVarEdit, Label): "Fake Edit Widget, polymorphic to TextEdit and Pmw.EntryField" def __init__(self, master, **kws): AbstractSingleVarEdit.__init__(self) kws.setdefault('width', 16) Label.__init__(self, master, **kws) def set(self, val): self.configure(text=val) setentry = set def get(self): return self.cget('text') getvalue = get def clear(self): self.set('') def component(self, name): return self def cget(self, opt): return Label.cget(self, opt.split('_')[-1]) class TextEdit(AbstractSingleVarEdit, Pmw.EntryField): def __init__(self, master, **kws): AbstractSingleVarEdit.__init__(self) kws.setdefault('entry_width', 16) kws.setdefault('modifiedcommand', self.save) Pmw.EntryField.__init__(self, master, **kws) def toolTipMaster(self): "Returns real Tk widget for use by ToolTip" return self.component('entry') def clear(self): self.delete(0, END) def set(self, text): self.delete(0, END) self.insert(END, text) self.save() def disable(self): self._state = DISABLED self.clear() self.configure(entry_state = DISABLED) def enable(self): self._state = NORMAL self.configure(entry_state = NORMAL) def focus_set(self): self.component('entry').focus_set() class CheckboxEdit(AbstractSingleVarEdit, Pmw.RadioSelect): def __init__(self, master, **kws): AbstractSingleVarEdit.__init__(self) kws.setdefault('buttontype', 'checkbutton') kws.setdefault('command', self.saveCallback) Pmw.RadioSelect.__init__(self, master, **kws) self.add('yes') def saveCallback(self, nothing=None, nothing2=None): self.save() def toolTipMaster(self): "Returns real Tk widget for use by ToolTip" return self.component('yes') def clear(self): self.setvalue([]) def get(self): if 'yes' in self.getvalue(): return 'yes' return 'no' def set(self, text): if text == 'yes': self.setvalue(['yes']) else: self.setvalue([]) self.save() def disable(self): self._state = DISABLED self.clear() def enable(self): self._state = NORMAL def focus_set(self): self.component('yes').focus_set() class TextComboEdit(AbstractSingleVarEdit, Pmw.ComboBox): def __init__(self, master, **kws): AbstractSingleVarEdit.__init__(self) if sys.platform != 'win32': # Thin Scrollbars: kws['scrolledlist_horizscrollbar_borderwidth'] = 1 kws['scrolledlist_horizscrollbar_width'] = 10 kws['scrolledlist_vertscrollbar_borderwidth'] = 1 kws['scrolledlist_vertscrollbar_width'] = 10 self.nomanualedit = False if kws.has_key('nomanualedit'): self.nomanualedit = kws['nomanualedit'] del kws['nomanualedit'] modifiedcommand = self.save if kws.has_key('modifiedcommand'): modifiedcommand = kws['modifiedcommand'] del kws['modifiedcommand'] if self.nomanualedit: kws['entryfield_pyclass'] = LabelPseudoEdit kws['entryfield_relief'] = SUNKEN kws['entryfield_borderwidth'] = 1 kws['selectioncommand'] = modifiedcommand else: kws['entryfield_modifiedcommand'] = modifiedcommand Pmw.ComboBox.__init__(self, master, scrolledlist_scrollmargin=0, history=0, unique=0, buttonaspect=0.75, hull_borderwidth=0, arrowbutton_highlightthickness=0, **kws) def toolTipMaster(self): "Returns real Tk widget for use by ToolTip" return self.component('entry') def clear(self): self.component("entryfield").clear() def get(self): return self.component("entryfield").getvalue() def set(self, val): self.setentry(val) def setlist(self, items): self.component("scrolledlist").setlist(items) def getlist(self): return self.component("scrolledlist").get() class FilenameEdit(AbstractSingleVarEdit, Frame): def __init__(self, master, **kws): AbstractSingleVarEdit.__init__(self) Frame.__init__(self, master, borderwidth=0) self.type = kws.get('type', 'open') self.filetypes = kws.get('filetypes', [("All Files", "*")]) self.showbasenameonly = kws.get('showbasenameonly', False) self.columnconfigure(0, weight=1) if self.showbasenameonly: self.edtFilename = LabelPseudoEdit(self, borderwidth=1, relief=SUNKEN) self._fullpath = '' else: self.edtFilename = TextEdit(self) self.edtFilename.grid(sticky=W+E) self.btnBrowse = Button(self, text='...', command=self._browse, padx=0, pady=0) ToolTip.ToolTip(self.btnBrowse, 'browse files') self.btnBrowse.grid(row=0, column=1) _browsedlg = None def _browse(self): if not self._browsedlg: dir, fname = os.path.split(self.get()) import tkFileDialog if self.type == 'saveas': self._browsedlg = tkFileDialog.SaveAs(filetypes = self.filetypes, initialfile=fname, initialdir=dir) else: self._browsedlg = tkFileDialog.Open(filetypes = self.filetypes, initialfile=fname, initialdir=dir) ret = self._browsedlg.show() if ret: self.set(ret) def toolTipMaster(self): "Returns real Tk widget for use by ToolTip" return self.edtFilename.toolTipMaster() def clear(self): self.edtFilename.clear() self._fullpath = '' def bindto(self, var): if self.showbasenameonly: AbstractSingleVarEdit.bindto(self, var) else: self.edtFilename.bindto(var) def set(self, val): if self.showbasenameonly: self._fullpath = val self.edtFilename.set(os.path.basename(val)) self.save() else: self.edtFilename.set(val) def get(self): if self.showbasenameonly: return self._fullpath else: return self.edtFilename.get() class FontEdit(AbstractSingleVarEdit, Frame): def __init__(self, master, **kws): AbstractSingleVarEdit.__init__(self) Frame.__init__(self, master, borderwidth=0) self.columnconfigure(0, weight=1) self.edtFontFamily = TextComboEdit(self, nomanualedit=True, modifiedcommand=self.save) import tkFont fontfamilies = list(tkFont.families()) fontfamilies.sort() self.edtFontFamily.setlist(fontfamilies) self.edtFontFamily.grid(rowspan=2, sticky=W+E) self.edtFontSize = Pmw.EntryField(self, validate={'validator':'integer', 'min':1, 'max':72}, entry_width=3, entry_justify=RIGHT, modifiedcommand=self.save) self.edtFontSize.grid(row=0, rowspan=2, column=1) btn = ArrowButton(self, direction='up', borderwidth=1, width=8, height=8, command=self._sizeIncr) btn.grid(row=0, column=2) btn = ArrowButton(self, direction='down', borderwidth=1, width=8, height=8, command=self._sizeDecr) btn.grid(row=1, column=2) self.edtFontSizeUnit = TextComboEdit(self, nomanualedit=True, modifiedcommand=self.save, entryfield_width=2) self.edtFontSizeUnit.setlist(['pt','px']) self.edtFontSizeUnit.grid(row=0, rowspan=2, column=3) self.edtFontModifiers = TextComboEdit(self, nomanualedit=True, modifiedcommand=self.save, entryfield_width=8) self.edtFontModifiers.setlist(['normal', 'bold', 'italic', 'bold italic']) self.edtFontModifiers.grid(row=0, rowspan=2, column=4) def _sizeIncr(self): text = self.edtFontSize.getvalue() if text != '': size = int(text); size += 1 else: size = 10 self.edtFontSize.setvalue(size) def _sizeDecr(self): text = self.edtFontSize.getvalue() if text != '': size = int(text); size += -1 else: size = 10 self.edtFontSize.setvalue(size) def clear(self): pass def set(self, val): try: family, size, mod = val except: debug.echo('FontEdit.set(): Illegal font specification.') family, size, mod = ('', '', '') self.edtFontFamily.set(family) if size[:1] == '-': size = size[1:] self.edtFontSizeUnit.set('px') else: self.edtFontSizeUnit.set('pt') self.edtFontSize.setvalue(size) if mod == '': mod = 'normal' self.edtFontModifiers.set(mod) def get(self): size = self.edtFontSize.getvalue() if self.edtFontSizeUnit.get() == 'px': size = '-'+size mod = self.edtFontModifiers.get() if mod == 'normal': mod = '' return (self.edtFontFamily.get(), size, mod) class MemoEdit(AbstractSingleVarEdit, Pmw.ScrolledText): def __init__(self, master, **kws): AbstractSingleVarEdit.__init__(self) kws.setdefault('text_width', 20) kws.setdefault('text_height', 4) kws.setdefault('text_wrap', WORD) # File extension for temp-File to open in # external editor: kws.setdefault('file_extension', '.txt') self._file_extension = kws['file_extension'] del kws['file_extension'] if sys.platform != 'win32': # Thin Scrollbars: kws['horizscrollbar_borderwidth'] = 1 kws['horizscrollbar_width'] = 10 kws['vertscrollbar_borderwidth'] = 1 kws['vertscrollbar_width'] = 10 Pmw.ScrolledText.__init__(self, master, scrollmargin=0, **kws) self.component('text').bind("", self.save) self.component('text').bind("", self.save) self.component('text').bind('', self.editInExternalEditor) def toolTipMaster(self): "Returns real Tk widget for use by ToolTip" return self.component('text') def get(self): return Pmw.ScrolledText.get(self, "1.0", END)[:-1] def clear(self): self.delete("1.0", END) def set(self, text): self.clear() self.insert(END, text, 'default') def editInExternalEditor(self, event=None): import tempfile fhandle, fname = tempfile.mkstemp(self._file_extension) os.write(fhandle, self.get().encode('utf-8', 'replace')) os.close(fhandle) try: # open external editor os.spawnlp(os.P_WAIT, 'gvim', 'gvim', '-f', fname) # read modified temporary file fileobj = open(fname, 'rb') self.set(unicode(fileobj.read(), 'utf-8', 'replace')) fileobj.close() finally: # finally delete the temporary file os.unlink(fname) class DateEdit(AbstractSingleVarEdit, Pmw.Counter): def __init__(self, master, **kws): AbstractSingleVarEdit.__init__(self) if kws.has_key('label_text'): kws['frame_borderwidth']=0 Pmw.Counter.__init__(self, master, entry_width = 10, entry_justify = LEFT, buttonaspect=0.5, datatype={'counter':'date', 'separator':'-', 'format':'ymd', 'yyyy':1}, entryfield_value = "0000-00-00", entryfield_validate = {'validator' : 'date', 'min' : '1700-01-01', 'max' : '2100-12-31', 'minstrict' : 0, 'maxstrict' : 0, 'format' : 'ymd', 'separator' : '-'}, entryfield_modifiedcommand=self.save, hull_borderwidth=0, #downarrow_borderwidth=1, downarrow_highlightthickness=0, #uparrow_borderwidth=1, uparrow_highlightthickness=0, **kws) def toolTipMaster(self): "Returns real Tk widget for use by ToolTip" return self.component('entry') def get(self): return self.getvalue() # inherited from Pmw.Counter / Pmw.EntryField def set(self, val): self.setvalue(val) class TimeEdit(AbstractSingleVarEdit, Pmw.Counter): def __init__(self, master, **kws): AbstractSingleVarEdit.__init__(self) if kws.has_key('label_text'): kws['frame_borderwidth']=0 Pmw.Counter.__init__(self, master, entry_width=8, entry_justify = LEFT, buttonaspect=0.5, datatype={'counter':'time', 'separator':':', 'time24':1}, entryfield_value = "00:00:00", entryfield_validate = {'validator' : 'time', 'separator' : ':'}, entryfield_modifiedcommand=self.save, hull_borderwidth=0, #downarrow_borderwidth=1, downarrow_highlightthickness=0, #uparrow_borderwidth=1, uparrow_highlightthickness=0, **kws) def toolTipMaster(self): "Returns real Tk widget for use by ToolTip" return self.component('entry') def get(self): return self.getvalue() # inherited from Pmw.Counter / Pmw.EntryField def set(self, val): self.setvalue(val) class MultiSelectButtons(Frame): from Set import Set def __init__(self, master, buttondefs, icons, iconsgrey): self.__state = NORMAL self.__var = None self._save_hooks = [] self.__set = self.Set() self.__buttondefs = buttondefs self.__icons = icons self.__iconsgrey = iconsgrey self.__buttons = {} Frame.__init__(self, master) self.__createWidgets() def __createWidgets(self): import ToolTip for name, descr in self.__buttondefs: def command(self=self, name=name): if name in self.__set: self.__set.remove(name) self.save() else: self.__set.add(name) self.save() self.__update() self.__buttons[name] = btn = Button(self, image=self.__iconsgrey[name], command=command, relief=FLAT) btn.pack(side=LEFT, fill=X, expand=TRUE) tooltip = ToolTip.ToolTip(btn, descr) def MouseEnter(event, self=self, name=name): if not name in self.__set: self.__buttons[name]["image"] = self.__icons[name] def MouseLeave(event, self=self, name=name): if not name in self.__set: self.__buttons[name]["image"] = self.__iconsgrey[name] btn.bind("", MouseEnter, add="+") btn.bind("", MouseLeave, add="+") def __update(self): for name, btn in zip(self.__buttons.keys(), self.__buttons.values()): if name in self.__set: btn["relief"] = SUNKEN btn["image"] = self.__icons[name] else: btn["relief"] = FLAT btn["image"] = self.__iconsgrey[name] def save(self, event=None): if self.__state != DISABLED and self.__var is not None: self.__var.assign(self.__set) for hook in self._save_hooks: hook() def add_save_hook(self, save_hook): self._save_hooks.append(save_hook) def bindto(self, var): if var: self.__var = var self.__set.assign(var) else: self.__set.assign(self.Set()) self.__var = None self.__update() def disable(self): self.__state = DISABLED for btn in self.__buttons.values(): btn["state"] = DISABLED self.__set.assign(self.Set()) self.__update() def enable(self): self.__state = NORMAL for btn in self.__buttons.values(): btn["state"] = NORMAL class MultiRecordEdit(Pmw.Group): "Edit Control e.g. for vCard.adr/tel/email" def __init__(self, master, recclass, title, rectitle=None): self.state = NORMAL self.__records = [] self.__recidx = 0 self.__recclass = recclass if not rectitle: rectitle = title self.__rectitle = rectitle Pmw.Group.__init__(self, master, tag_text = title) self.__createWidgets() def __createWidgets(self): master = self.interior() topbar = Frame(master) topbar.pack(side=TOP, fill=X) self.body = Frame(master) self.body.pack(side=BOTTOM, fill=BOTH, expand=1) topbar.columnconfigure(0, weight=1) self.__selRecord = Pmw.OptionMenu(topbar, items=[], command=self.__SelectRecordCallback, menubutton_padx=2, menubutton_pady=1) self.__selRecord.grid(row=0, column=0, sticky=W+E) self.__lblRecordCount = Label(topbar, text="(0)") self.__lblRecordCount.grid(row=0, column=1) self.__btnAddRecord = Button(topbar, image=IconImages.IconImages["new"], command=self._AddRecord) self.__btnAddRecord.grid(row=0, column=2) self.__btnDelRecord = Button(topbar, image=IconImages.IconImages["trash"], command=self._DelRecord) self.__btnDelRecord.grid(row=0, column=3) self.createBody() self.disableAll() def createBody(self): """Create Edit Widgets in self.body for selected Record To Be overwritten by SubClass""" raise NotImplementedError def bodyChildren(self): """return list of widgets created in createBody To Be overwritten by SubClass""" return [] def disableAll(self): for x in self.bodyChildren(): try: x.disable() except: pass self.__btnDelRecord["state"] = DISABLED self.state = DISABLED def enableAll(self): for x in self.bodyChildren(): try: x.enable() except: pass self.__btnDelRecord["state"] = NORMAL self.state = NORMAL def __updateMenu(self): items = [] for x in range(1, len(self.__records)+1): items.append(self.__rectitle + " %d" % x) self.__selRecord.setitems(items) self.__lblRecordCount["text"] = "(%d)" % len(items) def __SelectRecordCallback(self, str): self.__recidx = int(str.split()[1])-1 self.bindtorec(self.__records[self.__recidx]) def __Select(self, idx): self.__selRecord.invoke(idx) onRecordAdd = None def _AddRecord(self): self.__records.append(self.__recclass()) self.__updateMenu() self.__Select(len(self.__records)-1) self.enableAll() # Callback can be set by SubClass: if self.onRecordAdd: self.onRecordAdd(self.__records[-1]) onRecordDel = None def _DelRecord(self): del self.__records[self.__recidx] self.__updateMenu() if self.__records == []: self.disableAll() else: self.__Select(0) # Callback can be set by SubClass: if self.onRecordDel: self.onRecordDel() def bindto(self, records): if self.__records: # Save Old Binding (Rebind old): self.bindtorec(self.__records[self.__recidx]) if records is None: self.__records = [] self.__updateMenu() self.disableAll() self.bindtorec(None) else: self.__records = records self.__updateMenu() if len(self.__records)>0: self.enableAll() prefidx = 0 # Find the preferred record: for i in range(len(self.__records)): if self.__records[i].is_pref(): prefidx = i break self.__Select(prefidx) else: self.disableAll() self.bindtorec(None) def bindtorec(self, rec): "To be overridden by SubClass" pass class UnicodeVar(Variable): """Value holder for unicode string variables.""" _default = "" def __init__(self, master=None): """Construct a string variable. MASTER can be given as master widget.""" Variable.__init__(self, master) def get(self): """Return value of variable as unicode string.""" # Tk uses UTF8 => convert to Python Unicode String #return unicode(self._tk.globalgetvar(self._name), 'utf8') # At least in Python 2.3 this conversion is not needed: return self._tk.globalgetvar(self._name) class SearchableCombobox(Frame): def __init__(self, master, command, label_text=""): self.do_not_execute = 0 self._isPosted = 0 self.buttonaspect = 0.75 # list of (dispname, value, idx) tuples: self.list = [] # current index of self.list: self.curidx = 0 # list of (dispname, value, idx) tuples: self.__droplist = [] self.command = command Frame.__init__(self, master) self.columnconfigure(1, weight=1) self.label = label = Label(self, text=label_text) label.grid() self.entryvar = UnicodeVar() self.entryvar.trace("w", self.__entrychanged) self.entry = entry = Entry(self, textvariable=self.entryvar) self.normalBackground = self.entry['background'] entry.grid(column=1, row=0, sticky=W+E) self.button = button = Canvas(self, borderwidth=2, relief=RAISED, width=16, height=16, highlightthickness=0) button.grid(column=2, row=0) self._arrowRelief = self.button.cget('relief') self.popup = popup = Toplevel(self, borderwidth=1) popup.withdraw() popup.overrideredirect(1) kws = {} if sys.platform != 'win32': # Thin Scrollbars: kws['horizscrollbar_borderwidth'] = 1 kws['horizscrollbar_width'] = 10 kws['vertscrollbar_borderwidth'] = 1 kws['vertscrollbar_width'] = 10 self.scrolledlist = scrolledlist = Pmw.ScrolledListBox(popup, scrollmargin=0, hull_height=240, usehullsize=1, listbox_highlightthickness=0, **kws) scrolledlist.pack(fill=BOTH, expand=TRUE) self.listbox = listbox = scrolledlist.component("listbox") # Bind events to the arrow button. button.bind('<1>', self.invoke) button.bind('', self._drawArrow) button.bind('<3>', self._next) button.bind('', self._previous) button.bind('', self._next) button.bind('', self._previous) button.bind('', self.invoke) button.bind('', self.invoke) button.bind('', self.invoke) button.bind('', self.invoke) button.bind('', self.invoke) # Bind events to the dropdown window. popup.bind('', self._unpostList) popup.bind('', self._selectUnpost) popup.bind('', self._selectUnpost) popup.bind('', self._dropdownBtnRelease) popup.bind('', self._unpostOnNextRelease) # Bind events to the Tk listbox. listbox.bind('', self._unpostOnNextRelease) # Bind events to the Tk entry widget. entry.bind('<1>', self._unpostList) entry.bind('', self._entryfocusin) entry.bind('', self._resizeArrow) entry.bind('', self.invoke) entry.bind('', self.invoke) entry.bind('', self.invoke) entry.bind('', self.invoke) entry.bind('', self._next) entry.bind('', self._previous) # Need to unpost the popup if the entryfield is unmapped (eg: # its toplevel window is withdrawn) while the popup list is # displayed. entry.bind('', self._unpostList) def _next(self, event): if self._isPosted: self.listbox.focus_set() # Select (blue bg) first item: self.listbox.select_set(0) # Activate (underline) first item: self.listbox.activate(0) else: self.__select(self.curidx +1 ) self.entry.select_range(0, END) def _previous(self, event): self.__select(self.curidx -1 ) self.entry.select_range(0, END) def _entryfocusin(self, event): self.entry.select_range(0, END) def _drawArrow(self, event=None, sunken=0): arrow = self.button if sunken: self._arrowRelief = arrow.cget('relief') arrow.configure(relief = 'sunken') else: arrow.configure(relief = self._arrowRelief) direction = 'down' Pmw.drawarrow(arrow, self.entry['foreground'], direction, 'arrow') def invoke(self, event=None): self.__setdroplist() self._postList() def _postList(self, event = None): self._isPosted = 1 self._drawArrow(sunken=1) # Make sure that the arrow is displayed sunken. self.update_idletasks() x = self.entry.winfo_rootx() y = self.entry.winfo_rooty() + \ self.entry.winfo_height() w = self.entry.winfo_width() + self.button.winfo_width() h = self.listbox.winfo_height() sh = self.winfo_screenheight() if y + h > sh and y > sh / 2: y = self.entry.winfo_rooty() - h self.scrolledlist.configure(hull_width=w) Pmw.setgeometryanddeiconify(self.popup, '+%d+%d' % (x, y)) # Grab the popup, so that all events are delivered to it, and # set focus to the listbox, to make keyboard navigation # easier. #Pmw.pushgrab(self.popup, 1, self._unpostList) #self.listbox.focus_set() self._drawArrow() # Ignore the first release of the mouse button after posting the # dropdown list, unless the mouse enters the dropdown list. self._ignoreRelease = 1 def _dropdownBtnRelease(self, event): if (event.widget == self.scrolledlist.component('vertscrollbar') or event.widget == self.scrolledlist.component('horizscrollbar')): return if self._ignoreRelease: self._unpostOnNextRelease() return self._unpostList() if (event.x >= 0 and event.x < self.listbox.winfo_width() and event.y >= 0 and event.y < self.listbox.winfo_height()): self.__listboxselect() def _unpostOnNextRelease(self, event = None): self._ignoreRelease = 0 def _resizeArrow(self, event): bw = (int(self.button['borderwidth']) + int(self.button['highlightthickness'])) newHeight = self.entry.winfo_reqheight() - 2 * bw newWidth = int(newHeight * self.buttonaspect) self.button.configure(width=newWidth, height=newHeight) self._drawArrow() def _unpostList(self, event=None): if not self._isPosted: # It is possible to get events on an unposted popup. For # example, by repeatedly pressing the space key to post # and unpost the popup. The event may be # delivered to the popup window even though # Pmw.popgrab() has set the focus away from the # popup window. (Bug in Tk?) return # Restore the focus before withdrawing the window, since # otherwise the window manager may take the focus away so we # can't redirect it. Also, return the grab to the next active # window in the stack, if any. #Pmw.popgrab(self.popup) self.popup.withdraw() self._isPosted = 0 self._drawArrow() def _selectUnpost(self, event): self._unpostList() self.__listboxselect() def focus_set(self): self.entry.focus_set() def __find(self, searchstr): def matches(item, searchstr=searchstr.lower()): return item[0].lower()[:len(searchstr)] == searchstr # Return list of (dispname, value, listidx) tuples: ret = filter(matches, self.list) return ret def __setdroplist(self, list=None): if list is not None: # Droplist must be (dispname, value, listidx) tuples! self.__droplist = list else: # Default Droplist: All Items self.__droplist = self.list self.scrolledlist.setlist([name for name, value, idx in self.__droplist]) def __entrychanged(self, x, y, z): text = self.entryvar.get() if not text: return res = self.__find(text) if len(res) == 1 or (len(res) > 1 and res[0][0] == text): self.entry['background'] = self.normalBackground self.update_idletasks() dispname, value, idx = res[0] # Only one match found --> execute command: if not self.do_not_execute: self.__select(idx) self._unpostList() elif res: # More than one match self.entry['background'] = 'pink' self.update_idletasks() dispname, value, idx = res[0] self.curidx = idx # Display popup listbox: self.__setdroplist(res) self._postList() else: # No match self.entry['background'] = 'pink' self._unpostList() def __select(self, listidx): if listidx >= len(self.list): listidx = 0 elif listidx < 0: listidx = len(self.list)-1 dispname, value, idx = self.list[listidx] if self.command: self.command(value) self.do_not_execute = 1 self.entryvar.set(dispname) self.do_not_execute = 0 self.curidx = listidx def __listboxselect(self): sels = self.listbox.curselection() if len(sels) > 0: dispname, value, listidx = self.__droplist[int(sels[0])] self.__select(listidx) def set(self, text, do_not_execute=None): if do_not_execute: self.do_not_execute = 1 self.entryvar.set(text) self.do_not_execute = 0 def setlist(self, list): # list must be a list of (dispname, value, idx) tuples: self.list = zip([name for name, value in list], [value for name, value in list], xrange(len(list))) self.entryvar.set("") self.__setdroplist()