# Based on iwidgets2.2.0/scrolledlistbox.itk code.
class ScrolledListBox(Pmw
.MegaWidget
):
_classBindingsDefinedFor
= 0
def __init__(self
, parent
= None, **kw
):
# Define the megawidget options.
('dblclickcommand', None, None),
('hscrollmode', 'dynamic', self
._hscrollMode
),
('labelmargin', 0, INITOPT
),
('labelpos', None, INITOPT
),
('scrollmargin', 2, INITOPT
),
('selectioncommand', None, None),
('usehullsize', 0, INITOPT
),
('vscrollmode', 'dynamic', self
._vscrollMode
),
self
.defineoptions(kw
, optiondefs
)
# Initialise the base class (after defining the options).
Pmw
.MegaWidget
.__init
__(self
, parent
)
interior
= self
.interior()
interior
.grid_propagate(0)
# Create the listbox widget.
self
._listbox
= self
.createcomponent('listbox',
Tkinter
.Listbox
, (interior
,))
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 horizontal scrollbar
self
._horizScrollbar
= self
.createcomponent('horizscrollbar',
Tkinter
.Scrollbar
, (interior
,),
command
=self
._listbox
.xview
# Create the vertical scrollbar
self
._vertScrollbar
= self
.createcomponent('vertscrollbar',
Tkinter
.Scrollbar
, (interior
,),
command
=self
._listbox
.yview
self
.createlabel(interior
, childCols
= 3, childRows
= 3)
# Add the items specified by the initialisation option.
if type(items
) != types
.TupleType
:
apply(self
._listbox
.insert
, ('end',) + items
)
_registerScrolledList(self
._listbox
, self
)
# Establish the special class bindings if not already done.
# Also create bindings if the Tkinter default interpreter has
# changed. Use Tkinter._default_root to create class
# bindings, so that a reference to root is created by
# bind_class rather than a reference to self, which would
# prevent object cleanup.
theTag
= 'ScrolledListBoxTag'
if ScrolledListBox
._classBindingsDefinedFor
!= Tkinter
._default
_root
:
root
= Tkinter
._default
_root
_handleEvent(event
, 'double')
_handleEvent(event
, 'key')
_handleEvent(event
, 'release')
# Bind space and return keys and button 1 to the selectioncommand.
root
.bind_class(theTag
, '<Key-space>', keyEvent
)
root
.bind_class(theTag
, '<Key-Return>', keyEvent
)
root
.bind_class(theTag
, '<ButtonRelease-1>', releaseEvent
)
# Bind double button 1 click to the dblclickcommand.
root
.bind_class(theTag
, '<Double-ButtonRelease-1>', doubleEvent
)
ScrolledListBox
._classBindingsDefinedFor
= root
bindtags
= self
._listbox
.bindtags()
self
._listbox
.bindtags(bindtags
+ (theTag
,))
# Initialise instance variables.
self
._horizScrollbarOn
= 0
self
._vertScrollbarOn
= 0
self
._horizScrollbarNeeded
= 0
self
._vertScrollbarNeeded
= 0
# Check keywords and initialise options.
if self
.scrollTimer
is not None:
self
.after_cancel(self
.scrollTimer
)
_deregisterScrolledList(self
._listbox
)
Pmw
.MegaWidget
.destroy(self
)
# ======================================================================
def getcurselection(self
):
for sel
in self
.curselection():
rtn
.append(self
._listbox
.get(sel
))
return self
.getcurselection()
def setvalue(self
, textOrList
):
self
._listbox
.selection_clear(0, 'end')
listitems
= list(self
._listbox
.get(0, 'end'))
if type(textOrList
) == types
.StringType
:
if textOrList
in listitems
:
self
._listbox
.selection_set(listitems
.index(textOrList
))
raise ValueError, 'no such item "%s"' % textOrList
self
._listbox
.selection_set(listitems
.index(item
))
raise ValueError, 'no such item "%s"' % item
def setlist(self
, items
):
self
._listbox
.delete(0, 'end')
if type(items
) != types
.TupleType
:
apply(self
._listbox
.insert
, (0,) + items
)
# Override Tkinter.Listbox get method, so that if it is called with
# no arguments, return all list elements (consistent with other widgets).
def get(self
, first
=None, last
=None):
return self
._listbox
.get(0, 'end')
return self
._listbox
.get(first
, last
)
# ======================================================================
# The horizontal scroll mode has been configured.
mode
= self
['hscrollmode']
if not self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
if self
._horizScrollbarNeeded
!= self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
if self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
message
= 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
raise ValueError, message
self
._configureScrollCommands
()
# The vertical scroll mode has been configured.
mode
= self
['vscrollmode']
if not self
._vertScrollbarOn
:
self
._toggleVertScrollbar
()
if self
._vertScrollbarNeeded
!= self
._vertScrollbarOn
:
self
._toggleVertScrollbar
()
if self
._vertScrollbarOn
:
self
._toggleVertScrollbar
()
message
= 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
raise ValueError, message
self
._configureScrollCommands
()
# ======================================================================
def _configureScrollCommands(self
):
# If both scrollmodes are not dynamic we can save a lot of
# time by not having to create an idle job to handle the
# Clean up previous scroll commands to prevent memory leak.
tclCommandName
= str(self
._listbox
.cget('xscrollcommand'))
self
._listbox
.deletecommand(tclCommandName
)
tclCommandName
= str(self
._listbox
.cget('yscrollcommand'))
self
._listbox
.deletecommand(tclCommandName
)
if self
['hscrollmode'] == self
['vscrollmode'] == 'dynamic':
xscrollcommand
=self
._scrollBothLater
,
yscrollcommand
=self
._scrollBothLater
xscrollcommand
=self
._scrollXNow
,
yscrollcommand
=self
._scrollYNow
def _scrollXNow(self
, first
, last
):
self
._horizScrollbar
.set(first
, last
)
self
._horizScrollbarNeeded
= ((first
, last
) != ('0', '1'))
if self
['hscrollmode'] == 'dynamic':
if self
._horizScrollbarNeeded
!= self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
def _scrollYNow(self
, first
, last
):
self
._vertScrollbar
.set(first
, last
)
self
._vertScrollbarNeeded
= ((first
, last
) != ('0', '1'))
if self
['vscrollmode'] == 'dynamic':
if self
._vertScrollbarNeeded
!= self
._vertScrollbarOn
:
self
._toggleVertScrollbar
()
def _scrollBothLater(self
, first
, last
):
# Called by the listbox to set the horizontal or vertical
# scrollbar when it has scrolled or changed size or contents.
if self
.scrollTimer
is None:
self
.scrollTimer
= self
.after_idle(self
._scrollBothNow
)
def _scrollBothNow(self
):
# This performs the function of _scrollXNow and _scrollYNow.
# If one is changed, the other should be updated to match.
# Call update_idletasks to make sure that the containing frame
# has been resized before we attempt to set the scrollbars.
# Otherwise the scrollbars may be mapped/unmapped continuously.
self
._scrollRecurse
= self
._scrollRecurse
+ 1
self
._scrollRecurse
= self
._scrollRecurse
- 1
if self
._scrollRecurse
!= 0:
xview
= self
._listbox
.xview()
yview
= self
._listbox
.yview()
self
._horizScrollbar
.set(xview
[0], xview
[1])
self
._vertScrollbar
.set(yview
[0], yview
[1])
self
._horizScrollbarNeeded
= (xview
!= (0.0, 1.0))
self
._vertScrollbarNeeded
= (yview
!= (0.0, 1.0))
# If both horizontal and vertical scrollmodes are dynamic and
# currently only one scrollbar is mapped and both should be
# toggled, then unmap the mapped scrollbar. This prevents a
# continuous mapping and unmapping of the scrollbars.
if (self
['hscrollmode'] == self
['vscrollmode'] == 'dynamic' and
self
._horizScrollbarNeeded
!= self
._horizScrollbarOn
and
self
._vertScrollbarNeeded
!= self
._vertScrollbarOn
and
self
._vertScrollbarOn
!= self
._horizScrollbarOn
):
if self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
self
._toggleVertScrollbar
()
if self
['hscrollmode'] == 'dynamic':
if self
._horizScrollbarNeeded
!= self
._horizScrollbarOn
:
self
._toggleHorizScrollbar
()
if self
['vscrollmode'] == 'dynamic':
if self
._vertScrollbarNeeded
!= self
._vertScrollbarOn
:
self
._toggleVertScrollbar
()
def _toggleHorizScrollbar(self
):
self
._horizScrollbarOn
= not self
._horizScrollbarOn
interior
= self
.interior()
if self
._horizScrollbarOn
:
self
._horizScrollbar
.grid(row
= 4, column
= 2, sticky
= 'news')
interior
.grid_rowconfigure(3, minsize
= self
['scrollmargin'])
self
._horizScrollbar
.grid_forget()
interior
.grid_rowconfigure(3, minsize
= 0)
def _toggleVertScrollbar(self
):
self
._vertScrollbarOn
= not self
._vertScrollbarOn
interior
= self
.interior()
if self
._vertScrollbarOn
:
self
._vertScrollbar
.grid(row
= 2, column
= 4, sticky
= 'news')
interior
.grid_columnconfigure(3, minsize
= self
['scrollmargin'])
self
._vertScrollbar
.grid_forget()
interior
.grid_columnconfigure(3, minsize
= 0)
def _handleEvent(self
, event
, eventType
):
if eventType
== 'double':
command
= self
['dblclickcommand']
command
= self
['selectioncommand']
else: #eventType == 'release'
# Do not execute the command if the mouse was released
if (event
.x
< 0 or self
._listbox
.winfo_width() <= event
.x
or
event
.y
< 0 or self
._listbox
.winfo_height() <= event
.y
):
command
= self
['selectioncommand']
# Need to explicitly forward this to override the stupid
# (grid_)size method inherited from Tkinter.Frame.Grid.
return self
._listbox
.size()
# Need to explicitly forward this to override the stupid
# (grid_)bbox method inherited from Tkinter.Frame.Grid.
return self
._listbox
.bbox(index
)
Pmw
.forwardmethods(ScrolledListBox
, Tkinter
.Listbox
, '_listbox')
# ======================================================================
def _registerScrolledList(listbox
, scrolledList
):
# Register an ScrolledList widget for a Listbox widget
_listboxCache
[listbox
] = scrolledList
def _deregisterScrolledList(listbox
):
# Deregister a Listbox widget
del _listboxCache
[listbox
]
def _handleEvent(event
, eventType
):
# Forward events for a Listbox to it's ScrolledListBox
# A binding earlier in the bindtags list may have destroyed the
# megawidget, so need to check.
if _listboxCache
.has_key(event
.widget
):
_listboxCache
[event
.widget
]._handleEvent
(event
, eventType
)