blob: 6183be92ae9529c7f363b9b8d7b6add1be574dd4 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001"""Class browser.
2
3XXX TO DO:
4
5- reparse when source changed (maybe just a button would be OK?)
6 (or recheck on window popup)
7- add popup menu with more options (e.g. doc strings, base classes, imports)
8- show function argument list? (have to do pattern matching on source)
9- should the classes and methods lists also be in the module's menu bar?
10- add base classes to class browser tree
11"""
12
13import os
14import sys
David Scherer7aced172000-08-15 01:13:23 +000015import pyclbr
16
Florent Xiclunad630c042010-04-02 07:24:52 +000017from idlelib import PyShell
18from idlelib.WindowList import ListedToplevel
19from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas
20from idlelib.configHandler import idleConf
David Scherer7aced172000-08-15 01:13:23 +000021
22class ClassBrowser:
23
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040024 def __init__(self, flist, name, path, _htest=False):
David Scherer7aced172000-08-15 01:13:23 +000025 # XXX This API should change, if the file doesn't end in ".py"
26 # XXX the code here is bogus!
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040027 """
28 _htest - bool, change box when location running htest.
29 """
David Scherer7aced172000-08-15 01:13:23 +000030 self.name = name
31 self.file = os.path.join(path[0], self.name + ".py")
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040032 self._htest = _htest
David Scherer7aced172000-08-15 01:13:23 +000033 self.init(flist)
34
35 def close(self, event=None):
36 self.top.destroy()
37 self.node.destroy()
38
39 def init(self, flist):
40 self.flist = flist
41 # reset pyclbr
42 pyclbr._modules.clear()
43 # create top
44 self.top = top = ListedToplevel(flist.root)
45 top.protocol("WM_DELETE_WINDOW", self.close)
46 top.bind("<Escape>", self.close)
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040047 if self._htest: # place dialog below parent if running htest
48 top.geometry("+%d+%d" %
49 (flist.root.winfo_rootx(), flist.root.winfo_rooty() + 200))
David Scherer7aced172000-08-15 01:13:23 +000050 self.settitle()
51 top.focus_set()
52 # create scrolled canvas
Kurt B. Kaiser73360a32004-03-08 18:15:31 +000053 theme = idleConf.GetOption('main','Theme','name')
54 background = idleConf.GetHighlight(theme, 'normal')['background']
55 sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1)
David Scherer7aced172000-08-15 01:13:23 +000056 sc.frame.pack(expand=1, fill="both")
57 item = self.rootnode()
58 self.node = node = TreeNode(sc.canvas, None, item)
59 node.update()
60 node.expand()
61
62 def settitle(self):
63 self.top.wm_title("Class Browser - " + self.name)
64 self.top.wm_iconname("Class Browser")
65
66 def rootnode(self):
67 return ModuleBrowserTreeItem(self.file)
68
69class ModuleBrowserTreeItem(TreeItem):
70
71 def __init__(self, file):
72 self.file = file
73
74 def GetText(self):
75 return os.path.basename(self.file)
76
77 def GetIconName(self):
78 return "python"
79
80 def GetSubList(self):
81 sublist = []
82 for name in self.listclasses():
83 item = ClassBrowserTreeItem(name, self.classes, self.file)
84 sublist.append(item)
85 return sublist
86
87 def OnDoubleClick(self):
88 if os.path.normcase(self.file[-3:]) != ".py":
89 return
90 if not os.path.exists(self.file):
91 return
92 PyShell.flist.open(self.file)
93
94 def IsExpandable(self):
95 return os.path.normcase(self.file[-3:]) == ".py"
Kurt B. Kaiserd6c4c9e2001-07-12 23:54:20 +000096
David Scherer7aced172000-08-15 01:13:23 +000097 def listclasses(self):
98 dir, file = os.path.split(self.file)
99 name, ext = os.path.splitext(file)
100 if os.path.normcase(ext) != ".py":
101 return []
102 try:
103 dict = pyclbr.readmodule_ex(name, [dir] + sys.path)
104 except ImportError, msg:
105 return []
106 items = []
107 self.classes = {}
108 for key, cl in dict.items():
109 if cl.module == name:
110 s = key
Raymond Hettinger65500512003-01-19 02:37:41 +0000111 if hasattr(cl, 'super') and cl.super:
David Scherer7aced172000-08-15 01:13:23 +0000112 supers = []
113 for sup in cl.super:
114 if type(sup) is type(''):
115 sname = sup
116 else:
117 sname = sup.name
118 if sup.module != cl.module:
119 sname = "%s.%s" % (sup.module, sname)
120 supers.append(sname)
Kurt B. Kaisera2876442002-09-15 22:09:16 +0000121 s = s + "(%s)" % ", ".join(supers)
David Scherer7aced172000-08-15 01:13:23 +0000122 items.append((cl.lineno, s))
123 self.classes[s] = cl
124 items.sort()
125 list = []
126 for item, s in items:
127 list.append(s)
128 return list
129
130class ClassBrowserTreeItem(TreeItem):
131
132 def __init__(self, name, classes, file):
133 self.name = name
134 self.classes = classes
135 self.file = file
136 try:
137 self.cl = self.classes[self.name]
138 except (IndexError, KeyError):
139 self.cl = None
140 self.isfunction = isinstance(self.cl, pyclbr.Function)
141
142 def GetText(self):
143 if self.isfunction:
144 return "def " + self.name + "(...)"
145 else:
146 return "class " + self.name
147
148 def GetIconName(self):
149 if self.isfunction:
150 return "python"
151 else:
152 return "folder"
153
154 def IsExpandable(self):
155 if self.cl:
Kurt B. Kaiser0b743442003-01-20 04:49:37 +0000156 try:
157 return not not self.cl.methods
158 except AttributeError:
159 return False
David Scherer7aced172000-08-15 01:13:23 +0000160
161 def GetSubList(self):
162 if not self.cl:
163 return []
164 sublist = []
165 for name in self.listmethods():
166 item = MethodBrowserTreeItem(name, self.cl, self.file)
167 sublist.append(item)
168 return sublist
169
170 def OnDoubleClick(self):
171 if not os.path.exists(self.file):
172 return
173 edit = PyShell.flist.open(self.file)
174 if hasattr(self.cl, 'lineno'):
175 lineno = self.cl.lineno
176 edit.gotoline(lineno)
177
178 def listmethods(self):
179 if not self.cl:
180 return []
181 items = []
182 for name, lineno in self.cl.methods.items():
183 items.append((lineno, name))
184 items.sort()
185 list = []
186 for item, name in items:
187 list.append(name)
188 return list
189
190class MethodBrowserTreeItem(TreeItem):
191
192 def __init__(self, name, cl, file):
193 self.name = name
194 self.cl = cl
195 self.file = file
196
197 def GetText(self):
198 return "def " + self.name + "(...)"
199
200 def GetIconName(self):
201 return "python" # XXX
202
203 def IsExpandable(self):
204 return 0
205
206 def OnDoubleClick(self):
207 if not os.path.exists(self.file):
208 return
209 edit = PyShell.flist.open(self.file)
210 edit.gotoline(self.cl.methods[self.name])
211
Terry Jan Reedy62012fc2014-05-24 18:48:03 -0400212def _class_browser(parent): #Wrapper for htest
David Scherer7aced172000-08-15 01:13:23 +0000213 try:
214 file = __file__
215 except NameError:
216 file = sys.argv[0]
217 if sys.argv[1:]:
218 file = sys.argv[1]
219 else:
220 file = sys.argv[0]
221 dir, file = os.path.split(file)
222 name = os.path.splitext(file)[0]
Terry Jan Reedy62012fc2014-05-24 18:48:03 -0400223 flist = PyShell.PyShellFileList(parent)
224 ClassBrowser(flist, name, [dir], _htest=True)
Terry Jan Reedy985ef282014-05-27 02:47:38 -0400225 parent.mainloop()
David Scherer7aced172000-08-15 01:13:23 +0000226
227if __name__ == "__main__":
Terry Jan Reedy62012fc2014-05-24 18:48:03 -0400228 from idlelib.idle_test.htest import run
229 run(_class_browser)