# Based on iwidgets2.2.0/scrolledlistbox.itk code. import types import Tkinter import Pmw class ScrolledListBox(Pmw.MegaWidget): def __init__(self, parent = None, **kw): # Define the megawidget options. INITOPT = Pmw.INITOPT optiondefs = ( ('dblclickcommand', '', None), ('hscrollmode', 'dynamic', self._hscrollMode), ('items', (), INITOPT), ('labelmargin', 0, INITOPT), ('labelpos', None, INITOPT), ('scrollmargin', 2, INITOPT), ('selectioncommand', '', None), ('vscrollmode', 'dynamic', self._vscrollMode), ) self.defineoptions(kw, optiondefs) # Initialise the base class (after defining the options). Pmw.MegaWidget.__init__(self, parent) # Create the components. interior = self.interior() # Create the listbox widget. self._listbox = self.createcomponent('listbox', (), None, Tkinter.Listbox, (interior,), xscrollcommand=self._scrollListX, yscrollcommand=self._scrollListY) self._listbox.grid(row = 2, column = 2, sticky = 'news') interior.grid_rowconfigure(2, weight = 1, minsize = 0) interior.grid_columnconfigure(2, weight = 1, minsize = 0) # Create the vertical scrollbar self._vertScrollbar = self.createcomponent('vertscrollbar', (), 'Scrollbar', Tkinter.Scrollbar, (interior,), orient='vertical', command=self._listbox.yview) # Create the horizontal scrollbar self._horizScrollbar = self.createcomponent('horizscrollbar', (), 'Scrollbar', Tkinter.Scrollbar, (interior,), orient='horizontal', command=self._listbox.xview) self.createlabel(interior, childCols = 3, childRows = 3) # Add the items specified by the initialisation option. items = self['items'] if type(items) != types.TupleType: items = tuple(items) if len(items) > 0: apply(self._listbox.insert, ('end',) + items) tag = 'SLBSelect' + str(self) self.bind_class(tag, '<Control-Key-backslash>', self._makeSelection) self.bind_class(tag, '<Control-Key-slash>', self._makeSelection) self.bind_class(tag, '<Key-Escape>', self._makeSelection) self.bind_class(tag, '<Shift-Key-Select>', self._makeSelection) self.bind_class(tag, '<Control-Shift-Key-space>', self._makeSelection) self.bind_class(tag, '<Key-Select>', self._makeSelection) self.bind_class(tag, '<Key-space>', self._makeSelection) self.bind_class(tag, '<Control-Shift-Key-End>', self._makeSelection) self.bind_class(tag, '<Control-Key-End>', self._makeSelection) self.bind_class(tag, '<Control-Shift-Key-Home>', self._makeSelection) self.bind_class(tag, '<Control-Key-Home>', self._makeSelection) self.bind_class(tag, '<Shift-Key-Down>', self._makeSelection) self.bind_class(tag, '<Shift-Key-Up>', self._makeSelection) self.bind_class(tag, '<Control-Button-1>', self._makeSelection) self.bind_class(tag, '<Shift-Button-1>', self._makeSelection) self.bind_class(tag, '<ButtonRelease-1>', self._makeSelection) self.bind_class(tag, '<Double-1>', self._doubleClick) self._listbox.bindtags(self._listbox.bindtags() + (tag,)) # Initialise instance variables. self._vertMode = None self._horizMode = None # Check keywords and initialise options. self.initialiseoptions(ScrolledListBox) # ====================================================================== # Configuration methods. def _vscrollMode(self): mode = self['vscrollmode'] if mode == 'static': self._vertScrollbarDisplay(1) elif mode == 'dynamic' or mode == 'none': self._vertScrollbarDisplay(0) else: message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode raise ValueError, message def _hscrollMode(self): mode = self['hscrollmode'] if mode == 'static': self._horizScrollbarDisplay(1) elif mode == 'dynamic' or mode == 'none': self._horizScrollbarDisplay(0) else: message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode raise ValueError, message # ====================================================================== # Public methods. def getcurselection(self): rtn = [] for sel in self.curselection(): rtn.append(self._listbox.get(sel)) return tuple(rtn) def justify(self, direction): if direction == 'left': self._listbox.xview('moveto', 0) elif direction == 'right': self._listbox.xview('moveto', 1) elif direction == 'top': self._listbox.yview('moveto', 0) elif direction == 'bottom': self._listbox.yview('moveto', 1) else: validValues = 'left, right, top, or bottom' raise ValueError, \ 'bad justify argument "%s": should be %s' \ % (direction, validValues) def setlist(self, items): self._listbox.delete(0, 'end') if len(items) > 0: if type(items) != types.TupleType: items = tuple(items) apply(self._listbox.insert, (0,) + items) # ====================================================================== # Private methods. def _makeSelection(self, event): command = self['selectioncommand'] if callable(command): command() def _doubleClick(self, event): command = self['dblclickcommand'] if callable(command): command() def _vertScrollbarDisplay(self, mode): if mode != self._vertMode: self._vertMode = mode margin = self['scrollmargin'] interior = self.interior() if self._vertMode: self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news') interior.grid_columnconfigure(3, minsize = margin) else: self._vertScrollbar.grid_forget() interior.grid_columnconfigure(3, minsize = 0) def _horizScrollbarDisplay(self, mode): if mode != self._horizMode: self._horizMode = mode margin = self['scrollmargin'] interior = self.interior() if self._horizMode: self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news') interior.grid_rowconfigure(3, minsize = margin) else: self._horizScrollbar.grid_forget() interior.grid_rowconfigure(3, minsize = 0) def _scrollListX(self, first, last): self._horizScrollbar.set(first, last) if self['hscrollmode'] == 'dynamic': if first == '0' and last == '1': self._horizScrollbarDisplay(0) else: self._horizScrollbarDisplay(1) def _scrollListY(self, first, last): self._vertScrollbar.set(first, last) if self['vscrollmode'] == 'dynamic': if first == '0' and last == '1': self._vertScrollbarDisplay(0) else: self._vertScrollbarDisplay(1) # Need to explicitly forward this to override the stupid # (grid_)size method inherited from Tkinter.Frame.Grid. def size(self): return self._listbox.size() Pmw.forwardmethods(ScrolledListBox, Tkinter.Listbox, '_listbox')