blob: 4c711466415a1a3a435ae2bff617f762b66afc87 [file] [log] [blame]
Jack Jansen4892ab71996-09-24 15:35:50 +00001"""A simple Mac-only browse utility to peek at the inner data structures of Python."""
2# Minor modifications by Jack to facilitate incorporation in twit.
3
4# june 1996
5# Written by Just van Rossum <just@knoware.nl>, please send comments/improvements.
6# Loosely based on Jack Jansens's PICTbrowse.py, but depends on his fabulous FrameWork.py
7# XXX Some parts are *very* poorly solved. Will fix. Guido has to check if all the
8# XXX "python-peeking" is done correctly. I kindof reverse-engineered it ;-)
9
10# disclaimer: although I happen to be the brother of Python's father, programming is
11# not what I've been trained to do. So don't be surprised if you find anything that's not
12# as nice as it could be...
13
14# XXX to do:
15# Arrow key support
16# Copy & Paste?
17# MAIN_TEXT item should not contain (type); should be below or something.
18# MAIN_TEXT item should check if a string is binary or not: convert to '/000' style
19# or convert newlines.
20
21version = "1.0"
22
23import FrameWork
24import EasyDialogs
25import Dlg
26import Res
27import Qd
28import List
29import sys
Jack Jansenb280e2b1996-12-23 17:11:00 +000030from Types import *
Jack Jansen4892ab71996-09-24 15:35:50 +000031from QuickDraw import *
32import string
33import time
34import os
35
36# The initial object to start browsing with. Can be anything, but 'sys' makes kindof sense.
37start_object = sys
38
39# Resource definitions
Jack Jansenb280e2b1996-12-23 17:11:00 +000040ID_MAIN = 503
Jack Jansen4892ab71996-09-24 15:35:50 +000041NUM_LISTS = 4 # the number of lists used. could be changed, but the dlg item numbers should be consistent
42MAIN_TITLE = 3 # this is only the first text item, the other three ID's should be 5, 7 and 9
43MAIN_LIST = 4 # this is only the first list, the other three ID's should be 6, 8 and 10
44MAIN_TEXT = 11
45MAIN_LEFT = 1
46MAIN_RIGHT = 2
47MAIN_RESET = 12
48MAIN_CLOSE = 13
49MAIN_LINE = 14
50
51def Initialize():
52 # this bit ensures that this module will also work as an applet if the resources are
53 # in the resource fork of the applet
54 # stolen from Jack, so it should work(?!;-)
55 try:
56 # if this doesn't raise an error, we are an applet containing the necessary resources
57 # so we don't have to bother opening the resource file
58 dummy = Res.GetResource('DLOG', ID_MAIN)
59 except Res.Error:
60 savewd = os.getcwd()
61 ourparentdir = os.path.split(openresfile.func_code.co_filename)[0]
62 os.chdir(ourparentdir)
63 try:
64 Res.OpenResFile("mactwit_browse.rsrc")
65 except Res.Error, arg:
66 EasyDialogs.Message("Cannot open mactwit_browse.rsrc: "+arg[1])
67 sys.exit(1)
68 os.chdir(savewd)
69
70def main():
71 Initialize()
72 PythonBrowse()
73
74# this is all there is to it to make an application.
75class PythonBrowse(FrameWork.Application):
76 def __init__(self):
77 FrameWork.Application.__init__(self)
78 VarBrowser(self).open(start_object)
79 self.mainloop()
80
81 def do_about(self, id, item, window, event):
82 EasyDialogs.Message(self.__class__.__name__ + " version " + version + "\rby Just van Rossum")
83
84 def quit(self, *args):
85 raise self
86
87class MyList:
88 def __init__(self, wid, rect, itemnum):
89 # wid is the window (dialog) where our list is going to be in
90 # rect is it's item rectangle (as in dialog item)
91 # itemnum is the itemnumber in the dialog
92 self.rect = rect
93 rect2 = rect[0]+1, rect[1]+1, rect[2]-16, rect[3]-1 # Scroll bar space, that's 15 + 1, Jack!
94 self.list = List.LNew(rect2, (0, 0, 1, 0), (0,0), 0, wid,
95 0, 1, 0, 1)
96 self.wid = wid
97 self.active = 0
98 self.itemnum = itemnum
99
100 def setcontent(self, content, title = ""):
101 # first, gather some stuff
102 keylist = []
103 valuelist = []
104 thetype = type(content)
105 if thetype == DictType:
106 keylist = content.keys()
107 keylist.sort()
108 for key in keylist:
109 valuelist.append(content[key])
110 elif thetype == ListType:
111 keylist = valuelist = content
112 elif thetype == TupleType:
113
114 keylist = valuelist = []
115 for i in content:
116 keylist.append(i)
117 else:
118 # XXX help me! is all this correct? is there more I should consider???
119 # XXX is this a sensible way to do it in the first place????
120 # XXX I'm not familiar enough with Python's guts to be sure. GUIDOOOOO!!!
121 if hasattr(content, "__dict__"):
122 keylist = keylist + content.__dict__.keys()
123 if hasattr(content, "__methods__"):
124 keylist = keylist + content.__methods__
125 if hasattr(content, "__members__"):
126 keylist = keylist + content.__members__
127 if hasattr(content, "__class__"):
128 keylist.append("__class__")
129 if hasattr(content, "__bases__"):
130 keylist.append("__bases__")
131 if hasattr(content, "__name__"):
132 title = content.__name__
133 if "__name__" not in keylist:
134 keylist.append("__name__")
135 keylist.sort()
136 for key in keylist:
137 valuelist.append(getattr(content, key))
138 if content <> None:
139 title = title + "\r" + cleantype(content)
140 # now make that list!
141 tp, h, rect = self.wid.GetDialogItem(self.itemnum - 1)
142 Dlg.SetDialogItemText(h, title[:255])
143 self.list.LDelRow(0, 1)
144 self.list.LSetDrawingMode(0)
145 self.list.LAddRow(len(keylist), 0)
146 for i in range(len(keylist)):
147 self.list.LSetCell(str(keylist[i]), (0, i))
148 self.list.LSetDrawingMode(1)
149 self.list.LUpdate(self.wid.GetWindowPort().visRgn)
150 self.content = content
151 self.keylist = keylist
152 self.valuelist = valuelist
153 self.title = title
154
155 # draw a frame around the list, List Manager doesn't do that
156 def drawframe(self):
157 Qd.SetPort(self.wid)
158 Qd.FrameRect(self.rect)
159 rect2 = Qd.InsetRect(self.rect, -3, -3)
160 save = Qd.GetPenState()
161 Qd.PenSize(2, 2)
162 if self.active:
163 Qd.PenPat(Qd.qd.black)
164 else:
165 Qd.PenPat(Qd.qd.white)
166 # draw (or erase) an extra frame to indicate this is the acive list (or not)
167 Qd.FrameRect(rect2)
168 Qd.SetPenState(save)
169
170
171
172class VarBrowser(FrameWork.DialogWindow):
173 def open(self, start_object, title = ""):
174 FrameWork.DialogWindow.open(self, ID_MAIN)
175 if title <> "":
176 windowtitle = self.wid.GetWTitle()
177 self.wid.SetWTitle(windowtitle + " >> " + title)
178 else:
179 if hasattr(start_object, "__name__"):
180 windowtitle = self.wid.GetWTitle()
181 self.wid.SetWTitle(windowtitle + " >> " + str(getattr(start_object, "__name__")) )
182
183 self.SetPort()
184 Qd.TextFont(3)
185 Qd.TextSize(9)
186 self.lists = []
187 self.listitems = []
188 for i in range(NUM_LISTS):
189 self.listitems.append(MAIN_LIST + 2 * i) # dlg item numbers... have to be consistent
190 for i in self.listitems:
191 tp, h, rect = self.wid.GetDialogItem(i)
192 list = MyList(self.wid, rect, i)
193 self.lists.append(list)
194 self.leftover = []
195 self.rightover = []
196 self.setup(start_object, title)
197
198 def close(self):
199 self.lists = []
200 self.listitems = []
201 self.do_postclose()
202
203 def setup(self, start_object, title = ""):
204 # here we set the starting point for our expedition
205 self.start = start_object
206 self.lists[0].setcontent(start_object, title)
207 for list in self.lists[1:]:
208 list.setcontent(None)
209
210 def do_listhit(self, event, item):
211 (what, message, when, where, modifiers) = event
212 Qd.SetPort(self.wid)
213 where = Qd.GlobalToLocal(where)
214 for list in self.lists:
215 list.active = 0
216 list = self.lists[self.listitems.index(item)]
217 list.active = 1
218 for l in self.lists:
219 l.drawframe()
220
221 point = (0,0)
222 ok, point = list.list.LGetSelect(1, point)
223 if ok:
224 oldsel = point[1]
225 else:
226 oldsel = -1
227 # This should be: list.list.LClick(where, modifiers)
228 # Since the selFlags field of the list is not accessible from Python I have to do it like this.
229 # The effect is that you can't select more items by using shift or command.
230 list.list.LClick(where, 0)
231
232 index = self.listitems.index(item) + 1
233 point = (0,0)
234 ok, point = list.list.LGetSelect(1, point)
235 if oldsel == point[1]:
236 return # selection didn't change, do nothing.
237 if not ok:
238 for i in range(index, len(self.listitems)):
239 self.lists[i].setcontent(None)
240 self.rightover = []
241 return
242
243 if point[1] >= len(list.keylist):
244 return # XXX is this still necessary? is ok really true?
245 key = str(list.keylist[point[1]])
246 value = list.valuelist[point[1]]
247
248 self.settextitem("")
249 thetype = type(value)
250 if thetype == ListType or \
251 thetype == TupleType or \
252 thetype == DictType or \
253 hasattr(value, "__dict__") or \
254 hasattr(value, "__methods__") or \
255 hasattr(value, "__members__"): # XXX or, or... again: did I miss something?
256 if index >= len(self.listitems):
257 # we've reached the right side of our dialog. move everything to the left
258 # (by pushing the rightbutton...)
259 self.do_rightbutton(1)
260 index = index - 1
261 newlist = self.lists[index]
262 newlist.setcontent(value, key)
263 else:
264 index = index - 1
265 self.settextitem( str(value) + "\r" + cleantype(value))
266 for i in range(index + 1, len(self.listitems)):
267 self.lists[i].setcontent(None)
268 self.rightover = []
269
270 # helper to set the big text item at the bottom of the dialog.
271 def settextitem(self, text):
272 tp, h, rect = self.wid.GetDialogItem(MAIN_TEXT)
273 Dlg.SetDialogItemText(h, text[:255])
274
275 def do_rawupdate(self, window, event):
276 Qd.SetPort(self.wid)
277 iType, iHandle, iRect = window.GetDialogItem(MAIN_LINE)
278 Qd.FrameRect(iRect)
279 for list in self.lists:
280 Qd.FrameRect(list.rect)
281 if list.active:
282 # see MyList.drawframe
283 rect2 = Qd.InsetRect(list.rect, -3, -3)
284 save = Qd.GetPenState()
285 Qd.PenSize(2, 2)
286 Qd.FrameRect(rect2)
287 Qd.SetPenState(save)
288 for list in self.lists:
289 list.list.LUpdate(self.wid.GetWindowPort().visRgn)
290
291 def do_activate(self, activate, event):
292 for list in self.lists:
293 list.list.LActivate(activate)
294
295 # scroll everything one 'unit' to the left
296 # XXX I don't like the way this works. Too many 'manual' assignments
297 def do_rightbutton(self, force = 0):
298 if not force and self.rightover == []:
299 return
300 self.scroll(-1)
301 point = (0, 0)
302 ok, point = self.lists[0].list.LGetSelect(1, point)
303 self.leftover.append(point, self.lists[0].content, self.lists[0].title, self.lists[0].active)
304 for i in range(len(self.lists)-1):
305 point = (0, 0)
306 ok, point = self.lists[i+1].list.LGetSelect(1, point)
307 self.lists[i].setcontent(self.lists[i+1].content, self.lists[i+1].title)
308 self.lists[i].list.LSetSelect(ok, point)
309 self.lists[i].list.LAutoScroll()
310 self.lists[i].active = self.lists[i+1].active
311 self.lists[i].drawframe()
312 if len(self.rightover) > 0:
313 point, content, title, active = self.rightover[-1]
314 self.lists[-1].setcontent(content, title)
315 self.lists[-1].list.LSetSelect(1, point)
316 self.lists[-1].list.LAutoScroll()
317 self.lists[-1].active = active
318 self.lists[-1].drawframe()
319 del self.rightover[-1]
320 else:
321 self.lists[-1].setcontent(None)
322 self.lists[-1].active = 0
323 for list in self.lists:
324 list.drawframe()
325
326 # scroll everything one 'unit' to the right
327 def do_leftbutton(self):
328 if self.leftover == []:
329 return
330 self.scroll(1)
331 if self.lists[-1].content <> None:
332 point = (0, 0)
333 ok, point = self.lists[-1].list.LGetSelect(1, point)
334 self.rightover.append(point, self.lists[-1].content, self.lists[-1].title, self.lists[-1].active )
335 for i in range(len(self.lists)-1, 0, -1):
336 point = (0, 0)
337 ok, point = self.lists[i-1].list.LGetSelect(1, point)
338 self.lists[i].setcontent(self.lists[i-1].content, self.lists[i-1].title)
339 self.lists[i].list.LSetSelect(ok, point)
340 self.lists[i].list.LAutoScroll()
341 self.lists[i].active = self.lists[i-1].active
342 self.lists[i].drawframe()
343 if len(self.leftover) > 0:
344 point, content, title, active = self.leftover[-1]
345 self.lists[0].setcontent(content, title)
346 self.lists[0].list.LSetSelect(1, point)
347 self.lists[0].list.LAutoScroll()
348 self.lists[0].active = active
349 self.lists[0].drawframe()
350 del self.leftover[-1]
351 else:
352 self.lists[0].setcontent(None)
353 self.lists[0].active = 0
354
355 # create some visual feedback when 'scrolling' the lists to the left or to the right
356 def scroll(self, leftright): # leftright should be 1 or -1
357 # first, build a region containing all list rectangles
358 myregion = Qd.NewRgn()
359 mylastregion = Qd.NewRgn()
360 for list in self.lists:
361 AddRect2Rgn(list.rect, myregion)
362 AddRect2Rgn(list.rect, mylastregion)
363 # set the pen, but save it's state first
364 self.SetPort()
365 save = Qd.GetPenState()
366 Qd.PenPat(Qd.qd.gray)
367 Qd.PenMode(srcXor)
368 # how far do we have to scroll?
369 distance = self.lists[1].rect[0] - self.lists[0].rect[0]
370 step = 30
371 lasttime = time.clock() # for delay
372 # do it
373 for i in range(0, distance, step):
374 if i <> 0:
375 Qd.FrameRgn(mylastregion) # erase last region
376 Qd.OffsetRgn(mylastregion, step * leftright, 0)
377 # draw gray region
378 Qd.FrameRgn(myregion)
379 Qd.OffsetRgn(myregion, step * leftright, 0)
380 while time.clock() - lasttime < 0.05:
381 pass # delay
382 lasttime = time.clock()
383 # clean up after your dog
384 Qd.FrameRgn(mylastregion)
385 Qd.SetPenState(save)
386
387 def reset(self):
388 for list in self.lists:
389 point = (0,0)
390 ok, point = list.list.LGetSelect(1, point)
391 if ok:
392 sel = list.keylist[point[1]]
393 list.setcontent(list.content, list.title)
394 if ok:
395 list.list.LSetSelect(1, (0, list.keylist.index(sel)))
396 list.list.LAutoScroll()
397
398 def do_itemhit(self, item, event):
399 if item in self.listitems:
400 self.do_listhit(event, item)
401 elif item == MAIN_LEFT:
402 self.do_leftbutton()
403 elif item == MAIN_RIGHT:
404 self.do_rightbutton()
405 elif item == MAIN_CLOSE:
406 self.close()
407 elif item == MAIN_RESET:
408 self.reset()
409
410# helper function that returns a short string containing the type of an arbitrary object
411# eg: cleantype("wat is dit nu weer?") -> '(string)'
412def cleantype(obj):
413 # type() typically returns something like: <type 'string'>
414 items = string.split(str(type(obj)), "'")
415 if len(items) == 3:
416 return '(' + items[1] + ')'
417 else:
418 # just in case, I don't know.
419 return str(type(obj))
420
421# helper for VarBrowser.scroll
422def AddRect2Rgn(theRect, theRgn):
423 rRgn = Qd.NewRgn()
424 Qd.RectRgn(rRgn, theRect)
425 Qd.UnionRgn(rRgn, theRgn, theRgn)
426
427
428if __name__ == "__main__":
429 main()