|
1 | | -"""Primitive class browser. |
| 1 | +"""Class browser. |
2 | 2 |
|
3 | 3 | XXX TO DO: |
4 | 4 |
|
5 | | -- reparse when source changed |
| 5 | +- reparse when source changed (maybe just a button would be OK?) |
| 6 | + (or recheck on window popup) |
6 | 7 | - add popup menu with more options (e.g. doc strings, base classes, imports) |
7 | | -- show function argument list (have to do pattern matching on source) |
| 8 | +- show function argument list? (have to do pattern matching on source) |
8 | 9 | - should the classes and methods lists also be in the module's menu bar? |
| 10 | +- add base classes to class browser tree |
| 11 | +- make methodless classes inexpandable |
| 12 | +- make classless modules inexpandable |
| 13 | +
|
9 | 14 |
|
10 | 15 | """ |
11 | 16 |
|
12 | 17 | import os |
| 18 | +import sys |
13 | 19 | import string |
14 | 20 | import pyclbr |
15 | | -from Tkinter import * |
16 | | -import tkMessageBox |
17 | | -from WindowList import ListedToplevel |
18 | | -from Separator import HSeparator |
19 | | - |
20 | | -from ScrolledList import ScrolledList |
21 | 21 |
|
| 22 | +import PyShell |
| 23 | +from WindowList import ListedToplevel |
| 24 | +from TreeWidget import TreeNode, TreeItem, ScrolledCanvas |
22 | 25 |
|
23 | 26 | class ClassBrowser: |
24 | 27 |
|
25 | | - def __init__(self, flist, name, path=[]): |
26 | | - root = flist.root |
27 | | - try: |
28 | | - dict = pyclbr.readmodule(name, path) |
29 | | - except ImportError, msg: |
30 | | - tkMessageBox.showerror("Import error", str(msg), parent=root) |
31 | | - return |
32 | | - if not dict: |
33 | | - tkMessageBox.showerror("Nothing to browse", |
34 | | - "Module %s defines no classes" % name, parent=root) |
35 | | - return |
36 | | - self.flist = flist |
37 | | - self.dict = dict |
38 | | - self.root = root |
39 | | - self.top = top = ListedToplevel(root) |
40 | | - self.top.protocol("WM_DELETE_WINDOW", self.close) |
41 | | - self.top.bind("<Escape>", self.close) |
42 | | - top.wm_title("Class Browser - " + name) |
43 | | - top.wm_iconname("ClBrowser") |
44 | | - self.sepa = HSeparator(top) |
45 | | - leftframe, rightframe = self.sepa.parts() |
46 | | - self.leftframe = leftframe |
47 | | - self.rightframe = rightframe |
48 | | - leftframe.pack(side="left", fill="both", expand=1) |
49 | | - # Create help label |
50 | | - self.helplabel = Label(leftframe, text="Module %s" % name, |
51 | | - relief="groove", borderwidth=2) |
52 | | - self.helplabel.pack(fill="x") |
53 | | - # Create top frame, with scrollbar and listbox |
54 | | - self.classviewer = ClassViewer( |
55 | | - self.leftframe, self.flist, self) |
56 | | - # Load the classes |
57 | | - self.load_classes(dict, name) |
| 28 | + def __init__(self, flist, name, path): |
| 29 | + self.name = name |
| 30 | + self.file = os.path.join(path[0], self.name + ".py") |
| 31 | + self.init(flist) |
58 | 32 |
|
59 | 33 | def close(self, event=None): |
60 | | - self.classviewer = None |
61 | | - self.methodviewer = None |
62 | 34 | self.top.destroy() |
63 | 35 |
|
64 | | - def load_classes(self, dict, module): |
65 | | - self.classviewer.load_classes(dict, module) |
66 | | - if self.methodframe: |
67 | | - self.methodframe.destroy() |
68 | | - self.methodframe = None |
69 | | - self.methodviewer = None |
70 | | - |
71 | | - methodframe = None |
72 | | - methodhelplabel = None |
73 | | - methodviewer = None |
74 | | - |
75 | | - def show_methods(self, cl): |
76 | | - if not self.methodframe: |
77 | | - self.methodframe = Frame(self.rightframe) |
78 | | - self.methodframe.pack(side="right", expand=1, fill="both") |
79 | | - self.methodhelplabel = Label(self.methodframe, |
80 | | - relief="groove", borderwidth=2) |
81 | | - self.methodhelplabel.pack(fill="x") |
82 | | - self.methodviewer = MethodViewer(self.methodframe, self.flist) |
83 | | - self.methodhelplabel.config(text="Class %s" % cl.name) |
84 | | - self.methodviewer.load_methods(cl) |
85 | | - |
86 | | - |
87 | | -class ClassViewer(ScrolledList): |
88 | | - |
89 | | - def __init__(self, master, flist, browser): |
90 | | - ScrolledList.__init__(self, master, width=40) |
| 36 | + def init(self, flist): |
91 | 37 | self.flist = flist |
92 | | - self.browser = browser |
93 | | - |
94 | | - def load_classes(self, dict, module): |
95 | | - self.clear() |
96 | | - self.dict = dict |
| 38 | + # reset pyclbr |
| 39 | + pyclbr._modules.clear() |
| 40 | + # create top |
| 41 | + self.top = top = ListedToplevel(flist.root) |
| 42 | + top.protocol("WM_DELETE_WINDOW", self.close) |
| 43 | + top.bind("<Escape>", self.close) |
| 44 | + self.settitle() |
| 45 | + top.focus_set() |
| 46 | + # create scrolled canvas |
| 47 | + sc = ScrolledCanvas(top, bg="white", highlightthickness=0, takefocus=1) |
| 48 | + sc.frame.pack(expand=1, fill="both") |
| 49 | + item = self.rootnode() |
| 50 | + node = TreeNode(sc.canvas, None, item) |
| 51 | + node.update() |
| 52 | + node.expand() |
| 53 | + |
| 54 | + def settitle(self): |
| 55 | + self.top.wm_title("Class Browser - " + self.name) |
| 56 | + self.top.wm_iconname("Class Browser") |
| 57 | + |
| 58 | + def rootnode(self): |
| 59 | + return ModuleBrowserTreeItem(self.file) |
| 60 | + |
| 61 | +class ModuleBrowserTreeItem(TreeItem): |
| 62 | + |
| 63 | + def __init__(self, file): |
| 64 | + self.file = file |
| 65 | + |
| 66 | + def GetText(self): |
| 67 | + return os.path.basename(self.file) |
| 68 | + |
| 69 | + def GetIconName(self): |
| 70 | + return "python" |
| 71 | + |
| 72 | + def GetSubList(self): |
| 73 | + sublist = [] |
| 74 | + for name in self.listclasses(): |
| 75 | + item = ClassBrowserTreeItem(name, self.classes, self.file) |
| 76 | + sublist.append(item) |
| 77 | + return sublist |
| 78 | + |
| 79 | + def OnDoubleClick(self): |
| 80 | + if os.path.normcase(self.file[-3:]) != ".py": |
| 81 | + return |
| 82 | + if not os.path.exists(self.file): |
| 83 | + return |
| 84 | + PyShell.flist.open(self.file) |
| 85 | + |
| 86 | + def IsExpandable(self): |
| 87 | + return os.path.normcase(self.file[-3:]) == ".py" |
| 88 | + |
| 89 | + def listclasses(self): |
| 90 | + dir, file = os.path.split(self.file) |
| 91 | + name, ext = os.path.splitext(file) |
| 92 | + if os.path.normcase(ext) != ".py": |
| 93 | + return [] |
| 94 | + try: |
| 95 | + dict = pyclbr.readmodule(name, [dir] + sys.path) |
| 96 | + except ImportError, msg: |
| 97 | + return [] |
97 | 98 | items = [] |
98 | | - for key, value in dict.items(): |
99 | | - if value.module == module: |
100 | | - items.append((value.lineno, key, value)) |
| 99 | + self.classes = {} |
| 100 | + for key, cl in dict.items(): |
| 101 | + if cl.module == name: |
| 102 | + s = key |
| 103 | + if cl.super: |
| 104 | + supers = [] |
| 105 | + for sup in cl.super: |
| 106 | + if type(sup) is type(''): |
| 107 | + sname = sup |
| 108 | + else: |
| 109 | + sname = sup.name |
| 110 | + if sup.module != cl.module: |
| 111 | + sname = "%s.%s" % (sup.module, sname) |
| 112 | + supers.append(sname) |
| 113 | + s = s + "(%s)" % string.join(supers, ", ") |
| 114 | + items.append((cl.lineno, s)) |
| 115 | + self.classes[s] = cl |
101 | 116 | items.sort() |
102 | | - for lineno, key, value in items: |
103 | | - s = key |
104 | | - if value.super: |
105 | | - super = [] |
106 | | - for sup in value.super: |
107 | | - name = sup.name |
108 | | - if sup.module != value.module: |
109 | | - name = "%s.%s" % (sup.module, name) |
110 | | - super.append(name) |
111 | | - s = s + "(%s)" % string.join(super, ", ") |
112 | | - self.append(s) |
113 | | - |
114 | | - def getname(self, index): |
115 | | - name = self.listbox.get(index) |
116 | | - i = string.find(name, '(') |
117 | | - if i >= 0: |
118 | | - name = name[:i] |
119 | | - return name |
| 117 | + list = [] |
| 118 | + for item, s in items: |
| 119 | + list.append(s) |
| 120 | + return list |
120 | 121 |
|
121 | | - def getclass(self, index): |
122 | | - return self.dict[self.getname(index)] |
| 122 | +class ClassBrowserTreeItem(TreeItem): |
123 | 123 |
|
124 | | - def on_select(self, index): |
125 | | - self.show_methods(index) |
| 124 | + def __init__(self, name, classes, file): |
| 125 | + self.name = name |
| 126 | + self.classes = classes |
| 127 | + self.file = file |
126 | 128 |
|
127 | | - def on_double(self, index): |
128 | | - self.show_source(index) |
| 129 | + def GetText(self): |
| 130 | + return "class " + self.name |
129 | 131 |
|
130 | | - def show_methods(self, index): |
131 | | - cl = self.getclass(index) |
132 | | - self.browser.show_methods(cl) |
133 | | - |
134 | | - def show_source(self, index): |
135 | | - cl = self.getclass(index) |
136 | | - if os.path.isfile(cl.file): |
137 | | - edit = self.flist.open(cl.file) |
138 | | - edit.gotoline(cl.lineno) |
139 | | - |
140 | | - |
141 | | -class MethodViewer(ScrolledList): |
142 | | - |
143 | | - def __init__(self, master, flist): |
144 | | - ScrolledList.__init__(self, master) |
145 | | - self.flist = flist |
146 | | - |
147 | | - classinfo = None |
| 132 | + def IsExpandable(self): |
| 133 | + try: |
| 134 | + cl = self.classes[self.name] |
| 135 | + except (IndexError, KeyError): |
| 136 | + return 0 |
| 137 | + else: |
| 138 | + return not not cl.methods |
| 139 | + |
| 140 | + def GetSubList(self): |
| 141 | + sublist = [] |
| 142 | + for name in self.listmethods(): |
| 143 | + item = MethodBrowserTreeItem( |
| 144 | + name, self.classes[self.name], self.file) |
| 145 | + sublist.append(item) |
| 146 | + return sublist |
| 147 | + |
| 148 | + def OnDoubleClick(self): |
| 149 | + if not os.path.exists(self.file): |
| 150 | + return |
| 151 | + edit = PyShell.flist.open(self.file) |
| 152 | + if self.classes.has_key(self.name): |
| 153 | + cl = self.classes[self.name] |
| 154 | + else: |
| 155 | + name = self.name |
| 156 | + i = string.find(name, '(') |
| 157 | + if i < 0: |
| 158 | + return |
| 159 | + name = name[:i] |
| 160 | + if not self.classes.has_key(name): |
| 161 | + return |
| 162 | + cl = self.classes[name] |
| 163 | + if not hasattr(cl, 'lineno'): |
| 164 | + return |
| 165 | + lineno = cl.lineno |
| 166 | + edit.gotoline(lineno) |
148 | 167 |
|
149 | | - def load_methods(self, cl): |
150 | | - self.classinfo = cl |
151 | | - self.clear() |
| 168 | + def listmethods(self): |
| 169 | + try: |
| 170 | + cl = self.classes[self.name] |
| 171 | + except (IndexError, KeyError): |
| 172 | + return [] |
152 | 173 | items = [] |
153 | 174 | for name, lineno in cl.methods.items(): |
154 | 175 | items.append((lineno, name)) |
155 | 176 | items.sort() |
| 177 | + list = [] |
156 | 178 | for item, name in items: |
157 | | - self.append(name) |
| 179 | + list.append(name) |
| 180 | + return list |
| 181 | + |
| 182 | +class MethodBrowserTreeItem(TreeItem): |
158 | 183 |
|
159 | | - def click_event(self, event): |
160 | | - pass |
| 184 | + def __init__(self, name, cl, file): |
| 185 | + self.name = name |
| 186 | + self.cl = cl |
| 187 | + self.file = file |
161 | 188 |
|
162 | | - def on_double(self, index): |
163 | | - self.show_source(self.get(index)) |
| 189 | + def GetText(self): |
| 190 | + return "def " + self.name + "(...)" |
164 | 191 |
|
165 | | - def show_source(self, name): |
166 | | - if os.path.isfile(self.classinfo.file): |
167 | | - edit = self.flist.open(self.classinfo.file) |
168 | | - edit.gotoline(self.classinfo.methods[name]) |
| 192 | + def GetIconName(self): |
| 193 | + return "python" # XXX |
| 194 | + |
| 195 | + def IsExpandable(self): |
| 196 | + return 0 |
| 197 | + |
| 198 | + def OnDoubleClick(self): |
| 199 | + if not os.path.exists(self.file): |
| 200 | + return |
| 201 | + edit = PyShell.flist.open(self.file) |
| 202 | + edit.gotoline(self.cl.methods[self.name]) |
| 203 | + |
| 204 | +def main(): |
| 205 | + try: |
| 206 | + file = __file__ |
| 207 | + except NameError: |
| 208 | + file = sys.argv[0] |
| 209 | + if sys.argv[1:]: |
| 210 | + file = sys.argv[1] |
| 211 | + else: |
| 212 | + file = sys.argv[0] |
| 213 | + dir, file = os.path.split(file) |
| 214 | + name = os.path.splitext(file)[0] |
| 215 | + ClassBrowser(PyShell.flist, name, [dir]) |
| 216 | + if sys.stdin is sys.__stdin__: |
| 217 | + mainloop() |
| 218 | + |
| 219 | +if __name__ == "__main__": |
| 220 | + main() |
0 commit comments