historical/toontown-classic.git/panda/Pmw/Pmw_1_3/lib/PmwComboBox.py
2024-01-16 11:20:27 -06:00

382 lines
12 KiB
Python

# Based on iwidgets2.2.0/combobox.itk code.
import os
import string
import types
import Tkinter
import Pmw
class ComboBox(Pmw.MegaWidget):
def __init__(self, parent = None, **kw):
# Define the megawidget options.
INITOPT = Pmw.INITOPT
optiondefs = (
('autoclear', 0, INITOPT),
('buttonaspect', 1.0, INITOPT),
('dropdown', 1, INITOPT),
('fliparrow', 0, INITOPT),
('history', 1, INITOPT),
('labelmargin', 0, INITOPT),
('labelpos', None, INITOPT),
('listheight', 200, INITOPT),
('selectioncommand', None, None),
('sticky', 'ew', INITOPT),
('unique', 1, INITOPT),
)
self.defineoptions(kw, optiondefs)
# Initialise the base class (after defining the options).
Pmw.MegaWidget.__init__(self, parent)
# Create the components.
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')
if self['dropdown']:
self._isPosted = 0
interior.grid_rowconfigure(2, weight = 1)
# Create the arrow button.
self._arrowBtn = self.createcomponent('arrowbutton',
(), None,
Tkinter.Canvas, (interior,), borderwidth = 2,
relief = 'raised',
width = 16, height = 16)
if 'n' in self['sticky']:
sticky = 'n'
else:
sticky = ''
if 's' in self['sticky']:
sticky = sticky + 's'
self._arrowBtn.grid(column=3, row=2, sticky = sticky)
self._arrowRelief = self._arrowBtn.cget('relief')
# Create the label.
self.createlabel(interior, childCols=2)
# Create the dropdown window.
self._popup = self.createcomponent('popup',
(), None,
Tkinter.Toplevel, (interior,))
self._popup.withdraw()
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_borderwidth = 2,
hull_relief = 'raised',
hull_height = self['listheight'],
usehullsize = 1,
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
# displayed.
self._entryWidget.bind('<Unmap>', self._unpostList)
else:
# 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)
# Create the label.
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)
if self['history']:
self._entryfield.configure(command=self._addHistory)
# Check keywords and initialise options.
self.initialiseoptions()
def destroy(self):
if self['dropdown'] and self._isPosted:
Pmw.popgrab(self._popup)
Pmw.MegaWidget.destroy(self)
#======================================================================
# Public methods
def get(self, first = None, last=None):
if first is None:
return self._entryWidget.get()
else:
return self._list.get(first, last)
def invoke(self):
if self['dropdown']:
self._postList()
else:
return self._selectCmd()
def selectitem(self, index, setentry=1):
if type(index) == types.StringType:
text = index
items = self._list.get(0, 'end')
if text in items:
index = list(items).index(text)
else:
raise IndexError, 'index "%s" not found' % text
elif setentry:
text = self._list.get(0, 'end')[index]
self._list.select_clear(0, 'end')
self._list.select_set(index, index)
self._list.activate(index)
self.see(index)
if setentry:
self._entryfield.setentry(text)
# Need to explicitly forward this to override the stupid
# (grid_)size method inherited from Tkinter.Frame.Grid.
def size(self):
return self._list.size()
# Need to explicitly forward this to override the stupid
# (grid_)bbox method inherited from Tkinter.Frame.Grid.
def bbox(self, index):
return self._list.bbox(index)
def clear(self):
self._entryfield.clear()
self._list.clear()
#======================================================================
# Private methods for both dropdown and simple comboboxes.
def _addHistory(self):
input = self._entryWidget.get()
if input != '':
index = None
if self['unique']:
# If item is already in list, select it and return.
items = self._list.get(0, 'end')
if input in items:
index = list(items).index(input)
if index is None:
index = self._list.index('end')
self._list.insert('end', input)
self.selectitem(index)
if self['autoclear']:
self._entryWidget.delete(0, 'end')
# Execute the selectioncommand on the new entry.
self._selectCmd()
def _next(self, event):
size = self.size()
if size <= 1:
return
cursels = self.curselection()
if len(cursels) == 0:
index = 0
else:
index = string.atoi(cursels[0])
if index == size - 1:
index = 0
else:
index = index + 1
self.selectitem(index)
def _previous(self, event):
size = self.size()
if size <= 1:
return
cursels = self.curselection()
if len(cursels) == 0:
index = size - 1
else:
index = string.atoi(cursels[0])
if index == 0:
index = size - 1
else:
index = index - 1
self.selectitem(index)
def _selectCmd(self, event=None):
sels = self.getcurselection()
if len(sels) == 0:
item = None
else:
item = sels[0]
self._entryfield.setentry(item)
cmd = self['selectioncommand']
if callable(cmd):
if event is None:
# Return result of selectioncommand for invoke() method.
return cmd(item)
else:
cmd(item)
#======================================================================
# Private methods for dropdown combobox.
def _drawArrow(self, event=None, sunken=0):
arrow = self._arrowBtn
if sunken:
self._arrowRelief = arrow.cget('relief')
arrow.configure(relief = 'sunken')
else:
arrow.configure(relief = self._arrowRelief)
if self._isPosted and self['fliparrow']:
direction = 'up'
else:
direction = 'down'
Pmw.drawarrow(arrow, self['entry_foreground'], direction, 'arrow')
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._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
# 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._list.component('vertscrollbar') or
event.widget == self._list.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._selectCmd()
def _unpostOnNextRelease(self, event = None):
self._ignoreRelease = 0
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)
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 <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?)
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._selectCmd()
Pmw.forwardmethods(ComboBox, Pmw.ScrolledListBox, '_list')
Pmw.forwardmethods(ComboBox, Pmw.EntryField, '_entryfield')