1"""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 15import pyclbr 16 17from idlelib import PyShell 18from idlelib.WindowList import ListedToplevel 19from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas 20from idlelib.configHandler import idleConf 21 22file_open = None # Method...Item and Class...Item use this. 23# Normally PyShell.flist.open, but there is no PyShell.flist for htest. 24 25class ClassBrowser: 26 27 def __init__(self, flist, name, path, _htest=False): 28 # XXX This API should change, if the file doesn't end in ".py" 29 # XXX the code here is bogus! 30 """ 31 _htest - bool, change box when location running htest. 32 """ 33 global file_open 34 if not _htest: 35 file_open = PyShell.flist.open 36 self.name = name 37 self.file = os.path.join(path[0], self.name + ".py") 38 self._htest = _htest 39 self.init(flist) 40 41 def close(self, event=None): 42 self.top.destroy() 43 self.node.destroy() 44 45 def init(self, flist): 46 self.flist = flist 47 # reset pyclbr 48 pyclbr._modules.clear() 49 # create top 50 self.top = top = ListedToplevel(flist.root) 51 top.protocol("WM_DELETE_WINDOW", self.close) 52 top.bind("<Escape>", self.close) 53 if self._htest: # place dialog below parent if running htest 54 top.geometry("+%d+%d" % 55 (flist.root.winfo_rootx(), flist.root.winfo_rooty() + 200)) 56 self.settitle() 57 top.focus_set() 58 # create scrolled canvas 59 theme = idleConf.CurrentTheme() 60 background = idleConf.GetHighlight(theme, 'normal')['background'] 61 sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1) 62 sc.frame.pack(expand=1, fill="both") 63 item = self.rootnode() 64 self.node = node = TreeNode(sc.canvas, None, item) 65 node.update() 66 node.expand() 67 68 def settitle(self): 69 self.top.wm_title("Class Browser - " + self.name) 70 self.top.wm_iconname("Class Browser") 71 72 def rootnode(self): 73 return ModuleBrowserTreeItem(self.file) 74 75class ModuleBrowserTreeItem(TreeItem): 76 77 def __init__(self, file): 78 self.file = file 79 80 def GetText(self): 81 return os.path.basename(self.file) 82 83 def GetIconName(self): 84 return "python" 85 86 def GetSubList(self): 87 sublist = [] 88 for name in self.listclasses(): 89 item = ClassBrowserTreeItem(name, self.classes, self.file) 90 sublist.append(item) 91 return sublist 92 93 def OnDoubleClick(self): 94 if os.path.normcase(self.file[-3:]) != ".py": 95 return 96 if not os.path.exists(self.file): 97 return 98 PyShell.flist.open(self.file) 99 100 def IsExpandable(self): 101 return os.path.normcase(self.file[-3:]) == ".py" 102 103 def listclasses(self): 104 dir, file = os.path.split(self.file) 105 name, ext = os.path.splitext(file) 106 if os.path.normcase(ext) != ".py": 107 return [] 108 try: 109 dict = pyclbr.readmodule_ex(name, [dir] + sys.path) 110 except ImportError: 111 return [] 112 items = [] 113 self.classes = {} 114 for key, cl in dict.items(): 115 if cl.module == name: 116 s = key 117 if hasattr(cl, 'super') and cl.super: 118 supers = [] 119 for sup in cl.super: 120 if type(sup) is type(''): 121 sname = sup 122 else: 123 sname = sup.name 124 if sup.module != cl.module: 125 sname = "%s.%s" % (sup.module, sname) 126 supers.append(sname) 127 s = s + "(%s)" % ", ".join(supers) 128 items.append((cl.lineno, s)) 129 self.classes[s] = cl 130 items.sort() 131 list = [] 132 for item, s in items: 133 list.append(s) 134 return list 135 136class ClassBrowserTreeItem(TreeItem): 137 138 def __init__(self, name, classes, file): 139 self.name = name 140 self.classes = classes 141 self.file = file 142 try: 143 self.cl = self.classes[self.name] 144 except (IndexError, KeyError): 145 self.cl = None 146 self.isfunction = isinstance(self.cl, pyclbr.Function) 147 148 def GetText(self): 149 if self.isfunction: 150 return "def " + self.name + "(...)" 151 else: 152 return "class " + self.name 153 154 def GetIconName(self): 155 if self.isfunction: 156 return "python" 157 else: 158 return "folder" 159 160 def IsExpandable(self): 161 if self.cl: 162 try: 163 return not not self.cl.methods 164 except AttributeError: 165 return False 166 167 def GetSubList(self): 168 if not self.cl: 169 return [] 170 sublist = [] 171 for name in self.listmethods(): 172 item = MethodBrowserTreeItem(name, self.cl, self.file) 173 sublist.append(item) 174 return sublist 175 176 def OnDoubleClick(self): 177 if not os.path.exists(self.file): 178 return 179 edit = file_open(self.file) 180 if hasattr(self.cl, 'lineno'): 181 lineno = self.cl.lineno 182 edit.gotoline(lineno) 183 184 def listmethods(self): 185 if not self.cl: 186 return [] 187 items = [] 188 for name, lineno in self.cl.methods.items(): 189 items.append((lineno, name)) 190 items.sort() 191 list = [] 192 for item, name in items: 193 list.append(name) 194 return list 195 196class MethodBrowserTreeItem(TreeItem): 197 198 def __init__(self, name, cl, file): 199 self.name = name 200 self.cl = cl 201 self.file = file 202 203 def GetText(self): 204 return "def " + self.name + "(...)" 205 206 def GetIconName(self): 207 return "python" # XXX 208 209 def IsExpandable(self): 210 return 0 211 212 def OnDoubleClick(self): 213 if not os.path.exists(self.file): 214 return 215 edit = file_open(self.file) 216 edit.gotoline(self.cl.methods[self.name]) 217 218def _class_browser(parent): #Wrapper for htest 219 try: 220 file = __file__ 221 except NameError: 222 file = sys.argv[0] 223 if sys.argv[1:]: 224 file = sys.argv[1] 225 else: 226 file = sys.argv[0] 227 dir, file = os.path.split(file) 228 name = os.path.splitext(file)[0] 229 flist = PyShell.PyShellFileList(parent) 230 global file_open 231 file_open = flist.open 232 ClassBrowser(flist, name, [dir], _htest=True) 233 234if __name__ == "__main__": 235 from idlelib.idle_test.htest import run 236 run(_class_browser) 237