Generalized the scrolled list which is the base for the class and
method browser into a separate class in its own module.
diff --git a/Tools/idle/ClassBrowser.py b/Tools/idle/ClassBrowser.py
index 8db037a..ffbf508 100644
--- a/Tools/idle/ClassBrowser.py
+++ b/Tools/idle/ClassBrowser.py
@@ -9,11 +9,15 @@
 
 """
 
+import os
 import string
 import pyclbr
 from Tkinter import *
 import tkMessageBox
 
+from ScrolledList import ScrolledList
+
+
 class ClassBrowser:
     
     def __init__(self, flist, name):
@@ -32,44 +36,62 @@
         self.root = root
         self.top = top = Toplevel(root)
         self.top.protocol("WM_DELETE_WINDOW", self.close)
+        top.wm_title("Class browser")
         self.leftframe = leftframe = Frame(top)
         self.leftframe.pack(side="left", fill="both", expand=1)
-        top.wm_title("Class browser")
         # Create help label
-        self.helplabel = Label(leftframe,
-            text="Classes in module %s" % name,
-            borderwidth=2, relief="groove")
+        self.helplabel = Label(leftframe, text="Module %s" % name,
+                               relief="groove", borderwidth=2)
         self.helplabel.pack(fill="x")
         # Create top frame, with scrollbar and listbox
-        self.topframe = Frame(leftframe)
-        self.topframe.pack(fill="both", expand=1)
-        self.vbar = Scrollbar(self.topframe, name="vbar")
-        self.vbar.pack(side="right", fill="y")
-        self.listbox = Listbox(self.topframe, exportselection=0, 
-                               takefocus=1, width=30)
-        self.listbox.pack(expand=1, fill="both")
-        # Tie listbox and scrollbar together
-        self.vbar["command"] = self.listbox.yview
-        self.listbox["yscrollcommand"] = self.vbar.set
-        # Bind events to the list box
-        self.listbox.bind("<ButtonRelease-1>", self.click_event)
-	self.listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
-        ##self.listbox.bind("<ButtonPress-3>", self.popup_event)
-        self.listbox.bind("<Key-Up>", self.up_event)
-        self.listbox.bind("<Key-Down>", self.down_event)
+        self.classviewer = ClassViewer(
+            self.leftframe, self.flist, self)
         # Load the classes
-        self.loadclasses(dict, name)
+        self.load_classes(dict, name)
     
     def close(self):
+        self.classviewer = None
+        self.methodviewer = None
         self.top.destroy()
         
-    def loadclasses(self, dict, module):
+    def load_classes(self, dict, module):
+        self.classviewer.load_classes(dict, module)
+        if self.botframe:
+            self.botframe.destroy()
+            self.botframe = None
+        self.methodviewer = None
+
+    botframe = None
+    methodhelplabel = None
+    methodviewer = None
+    
+    def show_methods(self, cl):
+        if not self.botframe:
+            self.botframe = Frame(self.top)
+            self.botframe.pack(side="right", expand=1, fill="both")
+            self.methodhelplabel = Label(self.botframe,
+                               relief="groove", borderwidth=2)
+            self.methodhelplabel.pack(fill="x")
+            self.methodviewer = MethodViewer(self.botframe, self.flist)
+        self.methodhelplabel.config(text="Class %s" % cl.name)
+        self.methodviewer.load_methods(cl)
+
+
+class ClassViewer(ScrolledList):
+    
+    def __init__(self, master, flist, browser):
+        ScrolledList.__init__(self, master)
+        self.flist = flist
+        self.browser = browser
+        
+    def load_classes(self, dict, module):
+        self.clear()
+        self.dict = dict
         items = []
         for key, value in dict.items():
             if value.module == module:
                 items.append((value.lineno, key, value))
         items.sort()
-        l = self.listbox
         for lineno, key, value in items:
             s = key
             if value.super:
@@ -80,133 +102,60 @@
                         name = "%s.%s" % (sup.module, name)
                     super.append(name)
                 s = s + "(%s)" % string.join(super, ", ")
-            l.insert("end", s)
-        l.focus_set()
-        l.selection_clear(0, "end")
-        if self.botframe:
-            self.botframe.destroy()
-            self.botframe = None
-        self.methodviewer = None
+            self.append(s)
+    
+    def getname(self, index):
+        name = self.listbox.get(index)
+        i = string.find(name, '(')
+        if i >= 0:
+            name = name[:i]
+        return name
 
-    def click_event(self, event):
-        self.listbox.activate("@%d,%d" % (event.x, event.y))
-        index = self.listbox.index("active")
+    def getclass(self, index):
+        return self.dict[self.getname(index)]
+    
+    def on_select(self, index):
         self.show_methods(index)
     
-    def double_click_event(self, event):
-        self.listbox.activate("@%d,%d" % (event.x, event.y))
-        index = self.listbox.index("active")
+    def on_double(self, index):
         self.show_source(index)
     
-    def up_event(self, event):
-        index = self.listbox.index("active") - 1
-        if index < 0:
-            self.top.bell()
-            return "break"
-        self.show_methods(index)
-        return "break"
-    
-    def down_event(self, event):
-        index = self.listbox.index("active") + 1
-        if index >= self.listbox.index("end"):
-            self.top.bell()
-            return "break"
-        self.show_methods(index)
-        return "break"
-        
-    def show_source(self, index):
-        name = self.listbox.get(index)
-        i = string.find(name, '(')
-        if i >= 0:
-            name = name[:i]
-        cl = self.dict[name]
-        edit = self.flist.open(cl.file)
-        edit.gotoline(cl.lineno)
-
-    botframe = None
-    methodviewer = None
-    
     def show_methods(self, index):
-        self.listbox.selection_clear(0, "end")
-        self.listbox.selection_set(index)
-        self.listbox.activate(index)
-        self.listbox.see(index)
-        self.listbox.focus_set()
-        name = self.listbox.get(index)
-        i = string.find(name, '(')
-        if i >= 0:
-            name = name[:i]
-        cl = self.dict[name]
-        if not self.botframe:
-            self.botframe = Frame(self.top)
-            self.botframe.pack(expand=1, fill="both")
-        if not self.methodviewer:
-            self.methodviewer = MethodViewer(self.botframe, self.flist)
-        self.methodviewer.loadmethods(cl)
+        cl = self.getclass(index)
+        self.browser.show_methods(cl)
 
-class MethodViewer:
+    def show_source(self, index):
+        cl = self.getclass(index)
+        if os.path.isfile(cl.file):
+            edit = self.flist.open(cl.file)
+            edit.gotoline(cl.lineno)
+
+
+class MethodViewer(ScrolledList):
     
-    # XXX There's a pattern emerging here...
-    
-    def __init__(self, frame, flist):
-        self.frame = frame
+    def __init__(self, master, flist):
+        ScrolledList.__init__(self, master)
         self.flist = flist
-        # Create help label
-        self.helplabel = Label(frame,
-            text="Methods", borderwidth=2, relief="groove")
-        self.helplabel.pack(fill="x")
-        # Create top frame, with scrollbar and listbox
-        self.topframe = Frame(frame)
-        self.topframe.pack(fill="both", expand=1)
-        self.vbar = Scrollbar(self.topframe, name="vbar")
-        self.vbar.pack(side="right", fill="y")
-        self.listbox = Listbox(self.topframe, exportselection=0, 
-                               takefocus=1, width=30)
-        self.listbox.pack(expand=1, fill="both")
-        # Tie listbox and scrollbar together
-        self.vbar["command"] = self.listbox.yview
-        self.listbox["yscrollcommand"] = self.vbar.set
-        # Bind events to the list box
-        self.listbox.bind("<ButtonRelease-1>", self.click_event)
-	self.listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
-        ##self.listbox.bind("<ButtonPress-3>", self.popup_event)
-        self.listbox.bind("<Key-Up>", self.up_event)
-        self.listbox.bind("<Key-Down>", self.down_event)
         
     classinfo = None
     
-    def loadmethods(self, cl):
+    def load_methods(self, cl):
         self.classinfo = cl
-        self.helplabel.config(text="Methods of class %s" % cl.name)
-        l = self.listbox
-        l.delete(0, "end")
-        l.selection_clear(0, "end")
+        self.clear()
         items = []
         for name, lineno in cl.methods.items():
             items.append((lineno, name))
         items.sort()
         for item, name in items:
-            l.insert("end", name)
+            self.append(name)
 
     def click_event(self, event):
         pass
     
-    def double_click_event(self, event):
-        self.listbox.activate("@%d,%d" % (event.x, event.y))
-        index = self.listbox.index("active")
-        self.show_source(index)
+    def on_double(self, index):
+        self.show_source(self.get(index))
     
-    def up_event(self, event):
-        pass
-    
-    def down_event(self, event):
-        pass
-    
-    def show_source(self, index):
-        name = self.listbox.get(index)
-        i = string.find(name, '(')
-        if i >= 0:
-            name = name[:i]
-        edit = self.flist.open(self.classinfo.file)
-        edit.gotoline(self.classinfo.methods[name])
-        
+    def show_source(self, name):
+        if os.path.isfile(self.classinfo.file):
+            edit = self.flist.open(self.classinfo.file)
+            edit.gotoline(self.classinfo.methods[name])
diff --git a/Tools/idle/ScrolledList.py b/Tools/idle/ScrolledList.py
new file mode 100644
index 0000000..703cd0c
--- /dev/null
+++ b/Tools/idle/ScrolledList.py
@@ -0,0 +1,128 @@
+from Tkinter import *
+
+class ScrolledList:
+    
+    def __init__(self, master, **options):
+        # Create top frame, with scrollbar and listbox
+        self.master = master
+        self.frame = frame = Frame(master)
+        self.frame.pack(fill="both", expand=1)
+        self.vbar = vbar = Scrollbar(frame, name="vbar")
+        self.vbar.pack(side="right", fill="y")
+        self.listbox = listbox = Listbox(frame, exportselection=0)
+        if options:
+            listbox.configure(options)
+        listbox.pack(expand=1, fill="both")
+        # Tie listbox and scrollbar together
+        vbar["command"] = listbox.yview
+        listbox["yscrollcommand"] = vbar.set
+        # Bind events to the list box
+        listbox.bind("<ButtonRelease-1>", self.click_event)
+	listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
+        listbox.bind("<ButtonPress-3>", self.popup_event)
+        listbox.bind("<Key-Up>", self.up_event)
+        listbox.bind("<Key-Down>", self.down_event)
+        # Set the focus
+        listbox.focus_set()
+    
+    def clear(self):
+        self.listbox.delete(0, "end")
+    
+    def append(self, item):
+        self.listbox.insert("end", str(item))
+    
+    def get(self, index):
+        return self.listbox.get(index)
+
+    def click_event(self, event):
+        self.listbox.activate("@%d,%d" % (event.x, event.y))
+        index = self.listbox.index("active")
+        self.select(index)
+        self.on_select(index)
+        return "break"
+
+    def double_click_event(self, event):
+        index = self.listbox.index("active")
+        self.select(index)
+        self.on_double(index)
+        return "break"
+    
+    menu = None
+    
+    def popup_event(self, event):
+        if not self.menu:
+            self.make_menu()
+        menu = self.menu
+        self.listbox.activate("@%d,%d" % (event.x, event.y))
+        index = self.listbox.index("active")
+        self.select(index)
+        menu.tk_popup(event.x_root, event.y_root)
+
+    def make_menu(self):
+        menu = Menu(self.listbox, tearoff=0)
+        self.menu = menu
+        self.fill_menu()
+   
+    def up_event(self, event):
+        index = self.listbox.index("active")
+        if self.listbox.selection_includes(index):
+            index = index - 1
+        else:
+            index = self.listbox.size() - 1
+        if index < 0:
+            self.listbox.bell()
+        else:
+            self.select(index)
+            self.on_select(index)
+        return "break"
+        
+    def down_event(self, event):
+        index = self.listbox.index("active")
+        if self.listbox.selection_includes(index):
+            index = index + 1
+        else:
+            index = 0
+        if index >= self.listbox.size():
+            self.listbox.bell()
+        else:
+            self.select(index)
+            self.on_select(index)
+        return "break"
+    
+    def select(self, index):
+        self.listbox.focus_set()
+        self.listbox.activate(index)
+        self.listbox.selection_clear(0, "end")
+        self.listbox.selection_set(index)
+        self.listbox.see(index)
+    
+    # Methods to override for specific actions
+    
+    def fill_menu(self):
+        pass
+ 
+    def on_select(self, index):
+        pass
+        
+    def on_double(self, index):
+        pass
+
+
+def test():
+    root = Tk()
+    root.protocol("WM_DELETE_WINDOW", root.destroy)
+    class MyScrolledList(ScrolledList):
+        def fill_menu(self): self.menu.add_command(label="pass")
+        def on_select(self, index): print "select", self.get(index)
+        def on_double(self, index): print "double", self.get(index)
+    s = MyScrolledList(root)
+    for i in range(30):
+        s.append("item %02d" % i)
+    return root
+
+def main():
+    root = test()
+    root.mainloop()
+
+if __name__ == '__main__':
+    main()