Rewritten based on TreeWidget.py
diff --git a/Tools/idle/ClassBrowser.py b/Tools/idle/ClassBrowser.py
index f9b1655..0cf978e 100644
--- a/Tools/idle/ClassBrowser.py
+++ b/Tools/idle/ClassBrowser.py
@@ -1,168 +1,220 @@
-"""Primitive class browser.
+"""Class browser.
 
 XXX TO DO:
 
-- reparse when source changed
+- reparse when source changed (maybe just a button would be OK?)
+    (or recheck on window popup)
 - add popup menu with more options (e.g. doc strings, base classes, imports)
-- show function argument list (have to do pattern matching on source)
+- show function argument list? (have to do pattern matching on source)
 - should the classes and methods lists also be in the module's menu bar?
+- add base classes to class browser tree
+- make methodless classes inexpandable
+- make classless modules inexpandable
+
 
 """
 
 import os
+import sys
 import string
 import pyclbr
-from Tkinter import *
-import tkMessageBox
+
+import PyShell
 from WindowList import ListedToplevel
-from Separator import HSeparator
-
-from ScrolledList import ScrolledList
-
+from TreeWidget import TreeNode, TreeItem, ScrolledCanvas
 
 class ClassBrowser:
 
-    def __init__(self, flist, name, path=[]):
-        root = flist.root
-        try:
-            dict = pyclbr.readmodule(name, path)
-        except ImportError, msg:
-            tkMessageBox.showerror("Import error", str(msg), parent=root)
-            return
-        if not dict:
-            tkMessageBox.showerror("Nothing to browse",
-                "Module %s defines no classes" % name, parent=root)
-            return
-        self.flist = flist
-        self.dict = dict
-        self.root = root
-        self.top = top = ListedToplevel(root)
-        self.top.protocol("WM_DELETE_WINDOW", self.close)
-        self.top.bind("<Escape>", self.close)
-        top.wm_title("Class Browser - " + name)
-        top.wm_iconname("ClBrowser")
-        self.sepa = HSeparator(top)
-        leftframe, rightframe = self.sepa.parts()
-        self.leftframe = leftframe
-        self.rightframe = rightframe
-        leftframe.pack(side="left", fill="both", expand=1)
-        # Create help label
-        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.classviewer = ClassViewer(
-            self.leftframe, self.flist, self)
-        # Load the classes
-        self.load_classes(dict, name)
+    def __init__(self, flist, name, path):
+        self.name = name
+        self.file = os.path.join(path[0], self.name + ".py")
+        self.init(flist)
 
     def close(self, event=None):
-        self.classviewer = None
-        self.methodviewer = None
         self.top.destroy()
 
-    def load_classes(self, dict, module):
-        self.classviewer.load_classes(dict, module)
-        if self.methodframe:
-            self.methodframe.destroy()
-            self.methodframe = None
-        self.methodviewer = None
-
-    methodframe = None
-    methodhelplabel = None
-    methodviewer = None
-
-    def show_methods(self, cl):
-        if not self.methodframe:
-            self.methodframe = Frame(self.rightframe)
-            self.methodframe.pack(side="right", expand=1, fill="both")
-            self.methodhelplabel = Label(self.methodframe,
-                               relief="groove", borderwidth=2)
-            self.methodhelplabel.pack(fill="x")
-            self.methodviewer = MethodViewer(self.methodframe, 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, width=40)
+    def init(self, flist):
         self.flist = flist
-        self.browser = browser
+        # reset pyclbr
+        pyclbr._modules.clear()
+        # create top
+        self.top = top = ListedToplevel(flist.root)
+        top.protocol("WM_DELETE_WINDOW", self.close)
+        top.bind("<Escape>", self.close)
+        self.settitle()
+        top.focus_set()
+        # create scrolled canvas
+        sc = ScrolledCanvas(top, bg="white", highlightthickness=0, takefocus=1)
+        sc.frame.pack(expand=1, fill="both")
+        item = self.rootnode()
+        node = TreeNode(sc.canvas, None, item)
+        node.update()
+        node.expand()
 
-    def load_classes(self, dict, module):
-        self.clear()
-        self.dict = dict
+    def settitle(self):
+        self.top.wm_title("Class Browser - " + self.name)
+        self.top.wm_iconname("Class Browser")
+
+    def rootnode(self):
+        return ModuleBrowserTreeItem(self.file)
+
+class ModuleBrowserTreeItem(TreeItem):
+
+    def __init__(self, file):
+        self.file = file
+
+    def GetText(self):
+        return os.path.basename(self.file)
+
+    def GetIconName(self):
+        return "python"
+
+    def GetSubList(self):
+        sublist = []
+        for name in self.listclasses():
+            item = ClassBrowserTreeItem(name, self.classes, self.file)
+            sublist.append(item)
+        return sublist
+
+    def OnDoubleClick(self):
+        if os.path.normcase(self.file[-3:]) != ".py":
+            return
+        if not os.path.exists(self.file):
+            return
+        PyShell.flist.open(self.file)
+
+    def IsExpandable(self):
+        return os.path.normcase(self.file[-3:]) == ".py"
+    
+    def listclasses(self):
+        dir, file = os.path.split(self.file)
+        name, ext = os.path.splitext(file)
+        if os.path.normcase(ext) != ".py":
+            return []
+        try:
+            dict = pyclbr.readmodule(name, [dir] + sys.path)
+        except ImportError, msg:
+            return []
         items = []
-        for key, value in dict.items():
-            if value.module == module:
-                items.append((value.lineno, key, value))
+        self.classes = {}
+        for key, cl in dict.items():
+            if cl.module == name:
+                s = key
+                if cl.super:
+                    supers = []
+                    for sup in cl.super:
+                        if type(sup) is type(''):
+                            sname = sup
+                        else:
+                            sname = sup.name
+                            if sup.module != cl.module:
+                                sname = "%s.%s" % (sup.module, sname)
+                        supers.append(sname)
+                    s = s + "(%s)" % string.join(supers, ", ")
+                items.append((cl.lineno, s))
+                self.classes[s] = cl
         items.sort()
-        for lineno, key, value in items:
-            s = key
-            if value.super:
-                super = []
-                for sup in value.super:
-                    name = sup.name
-                    if sup.module != value.module:
-                        name = "%s.%s" % (sup.module, name)
-                    super.append(name)
-                s = s + "(%s)" % string.join(super, ", ")
-            self.append(s)
+        list = []
+        for item, s in items:
+            list.append(s)
+        return list
 
-    def getname(self, index):
-        name = self.listbox.get(index)
-        i = string.find(name, '(')
-        if i >= 0:
+class ClassBrowserTreeItem(TreeItem):
+
+    def __init__(self, name, classes, file):
+        self.name = name
+        self.classes = classes
+        self.file = file
+
+    def GetText(self):
+        return "class " + self.name
+
+    def IsExpandable(self):
+        try:
+            cl = self.classes[self.name]
+        except (IndexError, KeyError):
+            return 0
+        else:
+            return not not cl.methods
+
+    def GetSubList(self):
+        sublist = []
+        for name in self.listmethods():
+            item = MethodBrowserTreeItem(
+                name, self.classes[self.name], self.file)
+            sublist.append(item)
+        return sublist
+
+    def OnDoubleClick(self):
+        if not os.path.exists(self.file):
+            return
+        edit = PyShell.flist.open(self.file)
+        if self.classes.has_key(self.name):
+            cl = self.classes[self.name]
+        else:
+            name = self.name
+            i = string.find(name, '(')
+            if i < 0:
+                return
             name = name[:i]
-        return name
+            if not self.classes.has_key(name):
+                return
+            cl = self.classes[name]
+        if not hasattr(cl, 'lineno'):
+            return
+        lineno = cl.lineno
+        edit.gotoline(lineno)
 
-    def getclass(self, index):
-        return self.dict[self.getname(index)]
-
-    def on_select(self, index):
-        self.show_methods(index)
-
-    def on_double(self, index):
-        self.show_source(index)
-
-    def show_methods(self, index):
-        cl = self.getclass(index)
-        self.browser.show_methods(cl)
-
-    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):
-
-    def __init__(self, master, flist):
-        ScrolledList.__init__(self, master)
-        self.flist = flist
-
-    classinfo = None
-
-    def load_methods(self, cl):
-        self.classinfo = cl
-        self.clear()
+    def listmethods(self):
+        try:
+            cl = self.classes[self.name]
+        except (IndexError, KeyError):
+            return []
         items = []
         for name, lineno in cl.methods.items():
             items.append((lineno, name))
         items.sort()
+        list = []
         for item, name in items:
-            self.append(name)
+            list.append(name)
+        return list
 
-    def click_event(self, event):
-        pass
+class MethodBrowserTreeItem(TreeItem):
 
-    def on_double(self, index):
-        self.show_source(self.get(index))
+    def __init__(self, name, cl, file):
+        self.name = name
+        self.cl = cl
+        self.file = file
 
-    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])
+    def GetText(self):
+        return "def " + self.name + "(...)"
+
+    def GetIconName(self):
+        return "python" # XXX
+
+    def IsExpandable(self):
+        return 0
+
+    def OnDoubleClick(self):
+        if not os.path.exists(self.file):
+            return
+        edit = PyShell.flist.open(self.file)
+        edit.gotoline(self.cl.methods[self.name])
+
+def main():
+    try:
+        file = __file__
+    except NameError:
+        file = sys.argv[0]
+        if sys.argv[1:]:
+            file = sys.argv[1]
+        else:
+            file = sys.argv[0]
+    dir, file = os.path.split(file)
+    name = os.path.splitext(file)[0]
+    ClassBrowser(PyShell.flist, name, [dir])
+    if sys.stdin is sys.__stdin__:
+        mainloop()
+
+if __name__ == "__main__":
+    main()
diff --git a/Tools/idle/PathBrowser.py b/Tools/idle/PathBrowser.py
index 62470b2..7ea2410 100644
--- a/Tools/idle/PathBrowser.py
+++ b/Tools/idle/PathBrowser.py
@@ -1,58 +1,76 @@
 import os
 import sys
 import imp
-import string
-import tkMessageBox
 
-from MultiScrolledLists import MultiScrolledLists
+from TreeWidget import TreeItem
+from ClassBrowser import ClassBrowser, ModuleBrowserTreeItem
 
-class PathBrowser(MultiScrolledLists):
+class PathBrowser(ClassBrowser):
     
     def __init__(self, flist):
-        self.flist = flist
-        MultiScrolledLists.__init__(self, flist.root, 4)
-    
-    def longtitle(self):
-        return "Path Browser"
-    
-    def width(self, i):
-        return 30
-    
-    def height(self, i):
-        return 20
-    
-    def subtitle(self, i):
-        if i == 0:
-            return "Path Entries (sys.path)"
-        if i-1 >= len(self.path):
-            return ""
-        if i == 1:
-            return self.path[0]
-        if i == 2:
-            return "Classes in " + self.path[1]
-        if i == 3:
-            s = self.path[2]
-            i = string.find(s, "(")
-            if i > 0:
-                s = s[:i]
-            return "Methods of " + s
-        return ""
-    
-    def items(self, i):
-        if i == 0:
-            return sys.path
-        if i == 1:
-            return self.listmodules()
-        if i == 2:
-            return self.listclasses()
-        if i == 3:
-            return self.listmethods()
-    
-    def listmodules(self):
-        dir = self.path[0] or os.curdir
+        self.init(flist)
+
+    def settitle(self):
+        self.top.wm_title("Path Browser")
+        self.top.wm_iconname("Path Browser")
+
+    def rootnode(self):
+        return PathBrowserTreeItem()
+
+class PathBrowserTreeItem(TreeItem):
+
+    def GetText(self):
+        return "sys.path"
+
+    def GetSubList(self):
+        sublist = []
+        for dir in sys.path:
+            item = DirBrowserTreeItem(dir)
+            sublist.append(item)
+        return sublist
+
+class DirBrowserTreeItem(TreeItem):
+
+    def __init__(self, dir, packages=[]):
+        self.dir = dir
+        self.packages = packages
+
+    def GetText(self):
+        if not self.packages:
+            return self.dir
+        else:
+            return self.packages[-1] + ": package"
+
+    def GetSubList(self):
+        try:
+            names = os.listdir(self.dir or os.curdir)
+        except os.error:
+            return []
+        packages = []
+        for name in names:
+            file = os.path.join(self.dir, name)
+            if self.ispackagedir(file):
+                nn = os.path.normcase(name)
+                packages.append((nn, name, file))
+        packages.sort()
+        sublist = []
+        for nn, name, file in packages:
+            item = DirBrowserTreeItem(file, self.packages + [name])
+            sublist.append(item)
+        for nn, name in self.listmodules(names):
+            item = ModuleBrowserTreeItem(os.path.join(self.dir, name))
+            sublist.append(item)
+        return sublist
+
+    def ispackagedir(self, file):
+        if not os.path.isdir(file):
+            return 0
+        init = os.path.join(file, "__init__.py")
+        return os.path.exists(init)
+
+    def listmodules(self, allnames):
         modules = {}
         suffixes = imp.get_suffixes()
-        allnames = os.listdir(dir)
         sorted = []
         for suff, mode, flag in suffixes:
             i = -len(suff)
@@ -65,96 +83,13 @@
                         sorted.append((normed_name, name))
                         allnames.remove(name)
         sorted.sort()
-        names = []
-        for nn, name in sorted:
-            names.append(name)
-        return names
-    
-    def listclasses(self):
-        import pyclbr
-        dir = self.path[0]
-        file = self.path[1]
-        name, ext = os.path.splitext(file)
-        if os.path.normcase(ext) != ".py":
-            self.top.bell()
-            return []
-        try:
-            self.top.configure(cursor="watch")
-            self.top.update_idletasks()
-            try:
-                dict = pyclbr.readmodule(name, [dir] + sys.path)
-            finally:
-                self.top.configure(cursor="")
-        except ImportError, msg:
-            tkMessageBox.showerror("Import error", str(msg), parent=root)
-            return []
-        items = []
-        self.classes = {}
-        for key, cl in dict.items():
-            if cl.module == name:
-                s = key
-                if cl.super:
-                    supers = []
-                    for sup in cl.super:
-                        if type(sup) is type(''):
-                            sname = sup
-                        else:
-                            sname = sup.name
-                            if sup.module != cl.module:
-                                sname = "%s.%s" % (sup.module, sname)
-                        supers.append(sname)
-                    s = s + "(%s)" % string.join(supers, ", ")
-                items.append((cl.lineno, s))
-                self.classes[s] = cl
-        items.sort()
-        list = []
-        for item, s in items:
-            list.append(s)
-        return list
-    
-    def listmethods(self):
-        try:
-            cl = self.classes[self.path[2]]
-        except (IndexError, KeyError):
-            return []
-        items = []
-        for name, lineno in cl.methods.items():
-            items.append((lineno, name))
-        items.sort()
-        list = []
-        for item, name in items:
-            list.append(name)
-        return list
-    
-    def on_double(self, index, i):
-        if i == 0:
-            return
-        if i >= 1:
-            dir = self.path[0]
-            file = self.path[1]
-            name, ext = os.path.splitext(file)
-            if os.path.normcase(ext) != ".py":
-                self.top.bell()
-                return
-            fullname = os.path.join(dir, file)
-            edit = self.flist.open(fullname)
-            if i >= 2:
-                classname = self.path[2]
-                try:
-                    cl = self.classes[classname]
-                except KeyError:
-                    cl = None
-                else:
-                    if i == 2:
-                        edit.gotoline(cl.lineno)
-                    else:
-                        methodname = self.path[3]
-                        edit.gotoline(cl.methods[methodname])
-
+        return sorted
 
 def main():
     import PyShell
     PathBrowser(PyShell.flist)
+    if sys.stdin is sys.__stdin__:
+        mainloop()
 
 if __name__ == "__main__":
     main()