]>
Commit | Line | Data |
---|---|---|
1 | """Filling is the gui tree control through which a user can navigate | |
2 | the local namespace or any object.""" | |
3 | ||
4 | __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" | |
5 | __cvsid__ = "$Id$" | |
6 | __revision__ = "$Revision$"[11:-2] | |
7 | ||
8 | import wx | |
9 | ||
10 | import dispatcher | |
11 | import editwindow | |
12 | import inspect | |
13 | import introspect | |
14 | import keyword | |
15 | import sys | |
16 | import types | |
17 | from version import VERSION | |
18 | ||
19 | ||
20 | COMMONTYPES = [getattr(types, t) for t in dir(types) \ | |
21 | if not t.startswith('_') \ | |
22 | and t not in ('ClassType', 'InstanceType', 'ModuleType')] | |
23 | ||
24 | DOCTYPES = ('BuiltinFunctionType', 'BuiltinMethodType', 'ClassType', | |
25 | 'FunctionType', 'GeneratorType', 'InstanceType', | |
26 | 'LambdaType', 'MethodType', 'ModuleType', | |
27 | 'UnboundMethodType', 'method-wrapper') | |
28 | ||
29 | SIMPLETYPES = [getattr(types, t) for t in dir(types) \ | |
30 | if not t.startswith('_') and t not in DOCTYPES] | |
31 | ||
32 | del t | |
33 | ||
34 | try: | |
35 | COMMONTYPES.append(type(''.__repr__)) # Method-wrapper in version 2.2.x. | |
36 | except AttributeError: | |
37 | pass | |
38 | ||
39 | ||
40 | class FillingTree(wx.TreeCtrl): | |
41 | """FillingTree based on TreeCtrl.""" | |
42 | ||
43 | name = 'Filling Tree' | |
44 | revision = __revision__ | |
45 | ||
46 | def __init__(self, parent, id=-1, pos=wx.DefaultPosition, | |
47 | size=wx.DefaultSize, style=wx.TR_DEFAULT_STYLE, | |
48 | rootObject=None, rootLabel=None, rootIsNamespace=False, | |
49 | static=False): | |
50 | """Create FillingTree instance.""" | |
51 | wx.TreeCtrl.__init__(self, parent, id, pos, size, style) | |
52 | self.rootIsNamespace = rootIsNamespace | |
53 | import __main__ | |
54 | if rootObject is None: | |
55 | rootObject = __main__.__dict__ | |
56 | self.rootIsNamespace = True | |
57 | if rootObject is __main__.__dict__ and rootLabel is None: | |
58 | rootLabel = 'locals()' | |
59 | if not rootLabel: | |
60 | rootLabel = 'Ingredients' | |
61 | rootData = wx.TreeItemData(rootObject) | |
62 | self.item = self.root = self.AddRoot(rootLabel, -1, -1, rootData) | |
63 | self.SetItemHasChildren(self.root, self.objHasChildren(rootObject)) | |
64 | self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.OnItemExpanding, id=self.GetId()) | |
65 | self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, id=self.GetId()) | |
66 | self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, id=self.GetId()) | |
67 | self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated, id=self.GetId()) | |
68 | if not static: | |
69 | dispatcher.connect(receiver=self.push, signal='Interpreter.push') | |
70 | ||
71 | def push(self, command, more): | |
72 | """Receiver for Interpreter.push signal.""" | |
73 | self.display() | |
74 | ||
75 | def OnItemExpanding(self, event): | |
76 | """Add children to the item.""" | |
77 | busy = wx.BusyCursor() | |
78 | item = event.GetItem() | |
79 | if self.IsExpanded(item): | |
80 | return | |
81 | self.addChildren(item) | |
82 | # self.SelectItem(item) | |
83 | ||
84 | def OnItemCollapsed(self, event): | |
85 | """Remove all children from the item.""" | |
86 | busy = wx.BusyCursor() | |
87 | item = event.GetItem() | |
88 | # self.CollapseAndReset(item) | |
89 | # self.DeleteChildren(item) | |
90 | # self.SelectItem(item) | |
91 | ||
92 | def OnSelChanged(self, event): | |
93 | """Display information about the item.""" | |
94 | busy = wx.BusyCursor() | |
95 | self.item = event.GetItem() | |
96 | self.display() | |
97 | ||
98 | def OnItemActivated(self, event): | |
99 | """Launch a DirFrame.""" | |
100 | item = event.GetItem() | |
101 | text = self.getFullName(item) | |
102 | obj = self.GetPyData(item) | |
103 | frame = FillingFrame(parent=self, size=(600, 100), rootObject=obj, | |
104 | rootLabel=text, rootIsNamespace=False) | |
105 | frame.Show() | |
106 | ||
107 | def objHasChildren(self, obj): | |
108 | """Return true if object has children.""" | |
109 | if self.objGetChildren(obj): | |
110 | return True | |
111 | else: | |
112 | return False | |
113 | ||
114 | def objGetChildren(self, obj): | |
115 | """Return dictionary with attributes or contents of object.""" | |
116 | busy = wx.BusyCursor() | |
117 | otype = type(obj) | |
118 | if otype is types.DictType \ | |
119 | or str(otype)[17:23] == 'BTrees' and hasattr(obj, 'keys'): | |
120 | return obj | |
121 | d = {} | |
122 | if otype is types.ListType or otype is types.TupleType: | |
123 | for n in range(len(obj)): | |
124 | key = '[' + str(n) + ']' | |
125 | d[key] = obj[n] | |
126 | if otype not in COMMONTYPES: | |
127 | for key in introspect.getAttributeNames(obj): | |
128 | # Believe it or not, some attributes can disappear, | |
129 | # such as the exc_traceback attribute of the sys | |
130 | # module. So this is nested in a try block. | |
131 | try: | |
132 | d[key] = getattr(obj, key) | |
133 | except: | |
134 | pass | |
135 | return d | |
136 | ||
137 | def addChildren(self, item): | |
138 | self.DeleteChildren(item) | |
139 | obj = self.GetPyData(item) | |
140 | children = self.objGetChildren(obj) | |
141 | if not children: | |
142 | return | |
143 | keys = children.keys() | |
144 | keys.sort(lambda x, y: cmp(str(x).lower(), str(y).lower())) | |
145 | for key in keys: | |
146 | itemtext = str(key) | |
147 | # Show string dictionary items with single quotes, except | |
148 | # for the first level of items, if they represent a | |
149 | # namespace. | |
150 | if type(obj) is types.DictType \ | |
151 | and type(key) is types.StringType \ | |
152 | and (item != self.root \ | |
153 | or (item == self.root and not self.rootIsNamespace)): | |
154 | itemtext = repr(key) | |
155 | child = children[key] | |
156 | data = wx.TreeItemData(child) | |
157 | branch = self.AppendItem(parent=item, text=itemtext, data=data) | |
158 | self.SetItemHasChildren(branch, self.objHasChildren(child)) | |
159 | ||
160 | def display(self): | |
161 | item = self.item | |
162 | if not item: | |
163 | return | |
164 | if self.IsExpanded(item): | |
165 | self.addChildren(item) | |
166 | self.setText('') | |
167 | obj = self.GetPyData(item) | |
168 | if wx.Platform == '__WXMSW__': | |
169 | if obj is None: # Windows bug fix. | |
170 | return | |
171 | self.SetItemHasChildren(item, self.objHasChildren(obj)) | |
172 | otype = type(obj) | |
173 | text = '' | |
174 | text += self.getFullName(item) | |
175 | text += '\n\nType: ' + str(otype) | |
176 | try: | |
177 | value = str(obj) | |
178 | except: | |
179 | value = '' | |
180 | if otype is types.StringType or otype is types.UnicodeType: | |
181 | value = repr(obj) | |
182 | text += '\n\nValue: ' + value | |
183 | if otype not in SIMPLETYPES: | |
184 | try: | |
185 | text += '\n\nDocstring:\n\n"""' + \ | |
186 | inspect.getdoc(obj).strip() + '"""' | |
187 | except: | |
188 | pass | |
189 | if otype is types.InstanceType: | |
190 | try: | |
191 | text += '\n\nClass Definition:\n\n' + \ | |
192 | inspect.getsource(obj.__class__) | |
193 | except: | |
194 | pass | |
195 | else: | |
196 | try: | |
197 | text += '\n\nSource Code:\n\n' + \ | |
198 | inspect.getsource(obj) | |
199 | except: | |
200 | pass | |
201 | self.setText(text) | |
202 | ||
203 | def getFullName(self, item, partial=''): | |
204 | """Return a syntactically proper name for item.""" | |
205 | name = self.GetItemText(item) | |
206 | parent = None | |
207 | obj = None | |
208 | if item != self.root: | |
209 | parent = self.GetItemParent(item) | |
210 | obj = self.GetPyData(parent) | |
211 | # Apply dictionary syntax to dictionary items, except the root | |
212 | # and first level children of a namepace. | |
213 | if (type(obj) is types.DictType \ | |
214 | or str(type(obj))[17:23] == 'BTrees' \ | |
215 | and hasattr(obj, 'keys')) \ | |
216 | and ((item != self.root and parent != self.root) \ | |
217 | or (parent == self.root and not self.rootIsNamespace)): | |
218 | name = '[' + name + ']' | |
219 | # Apply dot syntax to multipart names. | |
220 | if partial: | |
221 | if partial[0] == '[': | |
222 | name += partial | |
223 | else: | |
224 | name += '.' + partial | |
225 | # Repeat for everything but the root item | |
226 | # and first level children of a namespace. | |
227 | if (item != self.root and parent != self.root) \ | |
228 | or (parent == self.root and not self.rootIsNamespace): | |
229 | name = self.getFullName(parent, partial=name) | |
230 | return name | |
231 | ||
232 | def setText(self, text): | |
233 | """Display information about the current selection.""" | |
234 | ||
235 | # This method will likely be replaced by the enclosing app to | |
236 | # do something more interesting, like write to a text control. | |
237 | print text | |
238 | ||
239 | def setStatusText(self, text): | |
240 | """Display status information.""" | |
241 | ||
242 | # This method will likely be replaced by the enclosing app to | |
243 | # do something more interesting, like write to a status bar. | |
244 | print text | |
245 | ||
246 | ||
247 | class FillingText(editwindow.EditWindow): | |
248 | """FillingText based on StyledTextCtrl.""" | |
249 | ||
250 | name = 'Filling Text' | |
251 | revision = __revision__ | |
252 | ||
253 | def __init__(self, parent, id=-1, pos=wx.DefaultPosition, | |
254 | size=wx.DefaultSize, style=wx.CLIP_CHILDREN, | |
255 | static=False): | |
256 | """Create FillingText instance.""" | |
257 | editwindow.EditWindow.__init__(self, parent, id, pos, size, style) | |
258 | # Configure various defaults and user preferences. | |
259 | self.SetReadOnly(True) | |
260 | self.SetWrapMode(True) | |
261 | self.SetMarginWidth(1, 0) | |
262 | if not static: | |
263 | dispatcher.connect(receiver=self.push, signal='Interpreter.push') | |
264 | ||
265 | def push(self, command, more): | |
266 | """Receiver for Interpreter.push signal.""" | |
267 | self.Refresh() | |
268 | ||
269 | def SetText(self, *args, **kwds): | |
270 | self.SetReadOnly(False) | |
271 | editwindow.EditWindow.SetText(self, *args, **kwds) | |
272 | self.SetReadOnly(True) | |
273 | ||
274 | ||
275 | class Filling(wx.SplitterWindow): | |
276 | """Filling based on wxSplitterWindow.""" | |
277 | ||
278 | name = 'Filling' | |
279 | revision = __revision__ | |
280 | ||
281 | def __init__(self, parent, id=-1, pos=wx.DefaultPosition, | |
282 | size=wx.DefaultSize, style=wx.SP_3D|wx.SP_LIVE_UPDATE, | |
283 | name='Filling Window', rootObject=None, | |
284 | rootLabel=None, rootIsNamespace=False, static=False): | |
285 | """Create a Filling instance.""" | |
286 | wx.SplitterWindow.__init__(self, parent, id, pos, size, style, name) | |
287 | ||
288 | self.tree = FillingTree(parent=self, rootObject=rootObject, | |
289 | rootLabel=rootLabel, | |
290 | rootIsNamespace=rootIsNamespace, | |
291 | static=static) | |
292 | self.text = FillingText(parent=self, static=static) | |
293 | ||
294 | wx.FutureCall(1, self.SplitVertically, self.tree, self.text, 200) | |
295 | ||
296 | self.SetMinimumPaneSize(1) | |
297 | ||
298 | # Override the filling so that descriptions go to FillingText. | |
299 | self.tree.setText = self.text.SetText | |
300 | ||
301 | # Display the root item. | |
302 | self.tree.SelectItem(self.tree.root) | |
303 | self.tree.display() | |
304 | ||
305 | self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnChanged) | |
306 | ||
307 | def OnChanged(self, event): | |
308 | #this is important: do not evaluate this event=> otherwise, splitterwindow behaves strange | |
309 | #event.Skip() | |
310 | pass | |
311 | ||
312 | ||
313 | def LoadSettings(self, config): | |
314 | pos = config.ReadInt('Sash/FillingPos', 200) | |
315 | wx.FutureCall(250, self.SetSashPosition, pos) | |
316 | zoom = config.ReadInt('View/Zoom/Filling', -99) | |
317 | if zoom != -99: | |
318 | self.text.SetZoom(zoom) | |
319 | ||
320 | def SaveSettings(self, config): | |
321 | config.WriteInt('Sash/FillingPos', self.GetSashPosition()) | |
322 | config.WriteInt('View/Zoom/Filling', self.text.GetZoom()) | |
323 | ||
324 | ||
325 | ||
326 | class FillingFrame(wx.Frame): | |
327 | """Frame containing the namespace tree component.""" | |
328 | ||
329 | name = 'Filling Frame' | |
330 | revision = __revision__ | |
331 | ||
332 | def __init__(self, parent=None, id=-1, title='PyFilling', | |
333 | pos=wx.DefaultPosition, size=(600, 400), | |
334 | style=wx.DEFAULT_FRAME_STYLE, rootObject=None, | |
335 | rootLabel=None, rootIsNamespace=False, static=False): | |
336 | """Create FillingFrame instance.""" | |
337 | wx.Frame.__init__(self, parent, id, title, pos, size, style) | |
338 | intro = 'PyFilling - The Tastiest Namespace Inspector' | |
339 | self.CreateStatusBar() | |
340 | self.SetStatusText(intro) | |
341 | import images | |
342 | self.SetIcon(images.getPyIcon()) | |
343 | self.filling = Filling(parent=self, rootObject=rootObject, | |
344 | rootLabel=rootLabel, | |
345 | rootIsNamespace=rootIsNamespace, | |
346 | static=static) | |
347 | # Override so that status messages go to the status bar. | |
348 | self.filling.tree.setStatusText = self.SetStatusText | |
349 | ||
350 | ||
351 | class App(wx.App): | |
352 | """PyFilling standalone application.""" | |
353 | ||
354 | def OnInit(self): | |
355 | wx.InitAllImageHandlers() | |
356 | self.fillingFrame = FillingFrame() | |
357 | self.fillingFrame.Show(True) | |
358 | self.SetTopWindow(self.fillingFrame) | |
359 | return True |