# Based on iwidgets2.2.0/combobox.itk code.
class ComboBox(Pmw
.MegaWidget
):
def __init__(self
, parent
= None, **kw
):
# Define the megawidget options.
('autoclear', 0, INITOPT
),
('buttonaspect', 1.0, INITOPT
),
('dropdown', 1, INITOPT
),
('fliparrow', 0, INITOPT
),
('labelmargin', 0, INITOPT
),
('labelpos', None, INITOPT
),
('listheight', 200, INITOPT
),
('selectioncommand', None, None),
('sticky', 'ew', INITOPT
),
self
.defineoptions(kw
, optiondefs
)
# Initialise the base class (after defining the options).
Pmw
.MegaWidget
.__init
__(self
, parent
)
interior
= self
.interior()
self
._entryfield
= self
.createcomponent('entryfield',
(('entry', 'entryfield_entry'),), None,
Pmw
.EntryField
, (interior
,))
self
._entryfield
.grid(column
=2, row
=2, sticky
=self
['sticky'])
interior
.grid_columnconfigure(2, weight
= 1)
self
._entryWidget
= self
._entryfield
.component('entry')
interior
.grid_rowconfigure(2, weight
= 1)
# Create the arrow button.
self
._arrowBtn
= self
.createcomponent('arrowbutton',
Tkinter
.Canvas
, (interior
,), borderwidth
= 2,
if 'n' in self
['sticky']:
if 's' in self
['sticky']:
self
._arrowBtn
.grid(column
=3, row
=2, sticky
= sticky
)
self
._arrowRelief
= self
._arrowBtn
.cget('relief')
self
.createlabel(interior
, childCols
=2)
# Create the dropdown window.
self
._popup
= self
.createcomponent('popup',
Tkinter
.Toplevel
, (interior
,))
self
._popup
.overrideredirect(1)
# Create the scrolled listbox inside the dropdown window.
self
._list
= self
.createcomponent('scrolledlist',
(('listbox', 'scrolledlist_listbox'),), None,
Pmw
.ScrolledListBox
, (self
._popup
,),
hull_height
= self
['listheight'],
listbox_exportselection
= 0)
self
._list
.pack(expand
=1, fill
='both')
self
.__listbox
= self
._list
.component('listbox')
# Bind events to the arrow button.
self
._arrowBtn
.bind('<1>', self
._postList
)
self
._arrowBtn
.bind('<Configure>', self
._drawArrow
)
self
._arrowBtn
.bind('<3>', self
._next
)
self
._arrowBtn
.bind('<Shift-3>', self
._previous
)
self
._arrowBtn
.bind('<Down>', self
._next
)
self
._arrowBtn
.bind('<Up>', self
._previous
)
self
._arrowBtn
.bind('<Control-n>', self
._next
)
self
._arrowBtn
.bind('<Control-p>', self
._previous
)
self
._arrowBtn
.bind('<Shift-Down>', self
._postList
)
self
._arrowBtn
.bind('<Shift-Up>', self
._postList
)
self
._arrowBtn
.bind('<F34>', self
._postList
)
self
._arrowBtn
.bind('<F28>', self
._postList
)
self
._arrowBtn
.bind('<space>', self
._postList
)
# Bind events to the dropdown window.
self
._popup
.bind('<Escape>', self
._unpostList
)
self
._popup
.bind('<space>', self
._selectUnpost
)
self
._popup
.bind('<Return>', self
._selectUnpost
)
self
._popup
.bind('<ButtonRelease-1>', self
._dropdownBtnRelease
)
self
._popup
.bind('<ButtonPress-1>', self
._unpostOnNextRelease
)
# Bind events to the Tk listbox.
self
.__listbox
.bind('<Enter>', self
._unpostOnNextRelease
)
# Bind events to the Tk entry widget.
self
._entryWidget
.bind('<Configure>', self
._resizeArrow
)
self
._entryWidget
.bind('<Shift-Down>', self
._postList
)
self
._entryWidget
.bind('<Shift-Up>', self
._postList
)
self
._entryWidget
.bind('<F34>', self
._postList
)
self
._entryWidget
.bind('<F28>', self
._postList
)
# Need to unpost the popup if the entryfield is unmapped (eg:
# its toplevel window is withdrawn) while the popup list is
self
._entryWidget
.bind('<Unmap>', self
._unpostList
)
# Create the scrolled listbox below the entry field.
self
._list
= self
.createcomponent('scrolledlist',
(('listbox', 'scrolledlist_listbox'),), None,
Pmw
.ScrolledListBox
, (interior
,),
selectioncommand
= self
._selectCmd
)
self
._list
.grid(column
=2, row
=3, sticky
='nsew')
self
.__listbox
= self
._list
.component('listbox')
# The scrolled listbox should expand vertically.
interior
.grid_rowconfigure(3, weight
= 1)
self
.createlabel(interior
, childRows
=2)
self
._entryWidget
.bind('<Down>', self
._next
)
self
._entryWidget
.bind('<Up>', self
._previous
)
self
._entryWidget
.bind('<Control-n>', self
._next
)
self
._entryWidget
.bind('<Control-p>', self
._previous
)
self
.__listbox
.bind('<Control-n>', self
._next
)
self
.__listbox
.bind('<Control-p>', self
._previous
)
self
._entryfield
.configure(command
=self
._addHistory
)
# Check keywords and initialise options.
if self
['dropdown'] and self
._isPosted
:
Pmw
.MegaWidget
.destroy(self
)
#======================================================================
def get(self
, first
= None, last
=None):
return self
._entryWidget
.get()
return self
._list
.get(first
, last
)
def selectitem(self
, index
, setentry
=1):
if type(index
) == types
.StringType
:
items
= self
._list
.get(0, 'end')
index
= list(items
).index(text
)
raise IndexError, 'index "%s" not found' % text
text
= self
._list
.get(0, 'end')[index
]
self
._list
.select_clear(0, 'end')
self
._list
.select_set(index
, index
)
self
._list
.activate(index
)
self
._entryfield
.setentry(text
)
# Need to explicitly forward this to override the stupid
# (grid_)size method inherited from Tkinter.Frame.Grid.
# Need to explicitly forward this to override the stupid
# (grid_)bbox method inherited from Tkinter.Frame.Grid.
return self
._list
.bbox(index
)
#======================================================================
# Private methods for both dropdown and simple comboboxes.
input = self
._entryWidget
.get()
# If item is already in list, select it and return.
items
= self
._list
.get(0, 'end')
index
= list(items
).index(input)
index
= self
._list
.index('end')
self
._list
.insert('end', input)
self
._entryWidget
.delete(0, 'end')
# Execute the selectioncommand on the new entry.
cursels
= self
.curselection()
index
= string
.atoi(cursels
[0])
def _previous(self
, event
):
cursels
= self
.curselection()
index
= string
.atoi(cursels
[0])
def _selectCmd(self
, event
=None):
sels
= self
.getcurselection()
self
._entryfield
.setentry(item
)
cmd
= self
['selectioncommand']
# Return result of selectioncommand for invoke() method.
#======================================================================
# Private methods for dropdown combobox.
def _drawArrow(self
, event
=None, sunken
=0):
self
._arrowRelief
= arrow
.cget('relief')
arrow
.configure(relief
= 'sunken')
arrow
.configure(relief
= self
._arrowRelief
)
if self
._isPosted
and self
['fliparrow']:
Pmw
.drawarrow(arrow
, self
['entry_foreground'], direction
, 'arrow')
def _postList(self
, event
= None):
self
._drawArrow
(sunken
=1)
# Make sure that the arrow is displayed sunken.
x
= self
._entryfield
.winfo_rootx()
y
= self
._entryfield
.winfo_rooty() + \
self
._entryfield
.winfo_height()
w
= self
._entryfield
.winfo_width() + self
._arrowBtn
.winfo_width()
h
= self
.__listbox
.winfo_height()
sh
= self
.winfo_screenheight()
if y
+ h
> sh
and y
> sh
/ 2:
y
= self
._entryfield
.winfo_rooty() - h
self
._list
.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
Pmw
.pushgrab(self
._popup
, 1, self
._unpostList
)
self
.__listbox
.focus_set()
# Ignore the first release of the mouse button after posting the
# dropdown list, unless the mouse enters the dropdown list.
def _dropdownBtnRelease(self
, event
):
if (event
.widget
== self
._list
.component('vertscrollbar') or
event
.widget
== self
._list
.component('horizscrollbar')):
self
._unpostOnNextRelease
()
if (event
.x
>= 0 and event
.x
< self
.__listbox
.winfo_width() and
event
.y
>= 0 and event
.y
< self
.__listbox
.winfo_height()):
def _unpostOnNextRelease(self
, event
= None):
def _resizeArrow(self
, event
):
bw
= (string
.atoi(self
._arrowBtn
['borderwidth']) +
string
.atoi(self
._arrowBtn
['highlightthickness']))
newHeight
= self
._entryfield
.winfo_reqheight() - 2 * bw
newWidth
= int(newHeight
* self
['buttonaspect'])
self
._arrowBtn
.configure(width
=newWidth
, height
=newHeight
)
def _unpostList(self
, event
=None):
# 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 <space> event may be
# delivered to the popup window even though
# Pmw.popgrab() has set the focus away from the
# popup window. (Bug in Tk?)
# 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.
def _selectUnpost(self
, event
):
Pmw
.forwardmethods(ComboBox
, Pmw
.ScrolledListBox
, '_list')
Pmw
.forwardmethods(ComboBox
, Pmw
.EntryField
, '_entryfield')