430 lines
13 KiB
Python
430 lines
13 KiB
Python
|
"""A simple Mac-only browse utility to peek at the inner data structures of Python."""
|
||
|
# Minor modifications by Jack to facilitate incorporation in twit.
|
||
|
|
||
|
# june 1996
|
||
|
# Written by Just van Rossum <just@knoware.nl>, please send comments/improvements.
|
||
|
# Loosely based on Jack Jansens's PICTbrowse.py, but depends on his fabulous FrameWork.py
|
||
|
# XXX Some parts are *very* poorly solved. Will fix. Guido has to check if all the
|
||
|
# XXX "python-peeking" is done correctly. I kindof reverse-engineered it ;-)
|
||
|
|
||
|
# disclaimer: although I happen to be the brother of Python's father, programming is
|
||
|
# not what I've been trained to do. So don't be surprised if you find anything that's not
|
||
|
# as nice as it could be...
|
||
|
|
||
|
# XXX to do:
|
||
|
# Arrow key support
|
||
|
# Copy & Paste?
|
||
|
# MAIN_TEXT item should not contain (type); should be below or something.
|
||
|
# MAIN_TEXT item should check if a string is binary or not: convert to '/000' style
|
||
|
# or convert newlines.
|
||
|
|
||
|
version = "1.0"
|
||
|
|
||
|
import FrameWork
|
||
|
import EasyDialogs
|
||
|
import Dlg
|
||
|
import Res
|
||
|
import Qd
|
||
|
import List
|
||
|
import sys
|
||
|
from Types import *
|
||
|
from QuickDraw import *
|
||
|
import string
|
||
|
import time
|
||
|
import os
|
||
|
|
||
|
# The initial object to start browsing with. Can be anything, but 'sys' makes kindof sense.
|
||
|
start_object = sys
|
||
|
|
||
|
# Resource definitions
|
||
|
ID_MAIN = 516
|
||
|
NUM_LISTS = 4 # the number of lists used. could be changed, but the dlg item numbers should be consistent
|
||
|
MAIN_TITLE = 3 # this is only the first text item, the other three ID's should be 5, 7 and 9
|
||
|
MAIN_LIST = 4 # this is only the first list, the other three ID's should be 6, 8 and 10
|
||
|
MAIN_TEXT = 11
|
||
|
MAIN_LEFT = 1
|
||
|
MAIN_RIGHT = 2
|
||
|
MAIN_RESET = 12
|
||
|
MAIN_CLOSE = 13
|
||
|
MAIN_LINE = 14
|
||
|
|
||
|
def Initialize():
|
||
|
# this bit ensures that this module will also work as an applet if the resources are
|
||
|
# in the resource fork of the applet
|
||
|
# stolen from Jack, so it should work(?!;-)
|
||
|
try:
|
||
|
# if this doesn't raise an error, we are an applet containing the necessary resources
|
||
|
# so we don't have to bother opening the resource file
|
||
|
dummy = Res.GetResource('DLOG', ID_MAIN)
|
||
|
except Res.Error:
|
||
|
savewd = os.getcwd()
|
||
|
ourparentdir = os.path.split(openresfile.func_code.co_filename)[0]
|
||
|
os.chdir(ourparentdir)
|
||
|
try:
|
||
|
Res.OpenResFile("mactwit_browse.rsrc")
|
||
|
except Res.Error, arg:
|
||
|
EasyDialogs.Message("Cannot open mactwit_browse.rsrc: "+arg[1])
|
||
|
sys.exit(1)
|
||
|
os.chdir(savewd)
|
||
|
|
||
|
def main():
|
||
|
Initialize()
|
||
|
PythonBrowse()
|
||
|
|
||
|
# this is all there is to it to make an application.
|
||
|
class PythonBrowse(FrameWork.Application):
|
||
|
def __init__(self):
|
||
|
FrameWork.Application.__init__(self)
|
||
|
VarBrowser(self).open(start_object)
|
||
|
self.mainloop()
|
||
|
|
||
|
def do_about(self, id, item, window, event):
|
||
|
EasyDialogs.Message(self.__class__.__name__ + " version " + version + "\rby Just van Rossum")
|
||
|
|
||
|
def quit(self, *args):
|
||
|
raise self
|
||
|
|
||
|
class MyList:
|
||
|
def __init__(self, wid, rect, itemnum):
|
||
|
# wid is the window (dialog) where our list is going to be in
|
||
|
# rect is it's item rectangle (as in dialog item)
|
||
|
# itemnum is the itemnumber in the dialog
|
||
|
self.rect = rect
|
||
|
rect2 = rect[0]+1, rect[1]+1, rect[2]-16, rect[3]-1 # Scroll bar space, that's 15 + 1, Jack!
|
||
|
self.list = List.LNew(rect2, (0, 0, 1, 0), (0,0), 0, wid,
|
||
|
0, 1, 0, 1)
|
||
|
self.wid = wid
|
||
|
self.active = 0
|
||
|
self.itemnum = itemnum
|
||
|
|
||
|
def setcontent(self, content, title = ""):
|
||
|
# first, gather some stuff
|
||
|
keylist = []
|
||
|
valuelist = []
|
||
|
thetype = type(content)
|
||
|
if thetype == DictType:
|
||
|
keylist = content.keys()
|
||
|
keylist.sort()
|
||
|
for key in keylist:
|
||
|
valuelist.append(content[key])
|
||
|
elif thetype == ListType:
|
||
|
keylist = valuelist = content
|
||
|
elif thetype == TupleType:
|
||
|
|
||
|
keylist = valuelist = []
|
||
|
for i in content:
|
||
|
keylist.append(i)
|
||
|
else:
|
||
|
# XXX help me! is all this correct? is there more I should consider???
|
||
|
# XXX is this a sensible way to do it in the first place????
|
||
|
# XXX I'm not familiar enough with Python's guts to be sure. GUIDOOOOO!!!
|
||
|
if hasattr(content, "__dict__"):
|
||
|
keylist = keylist + content.__dict__.keys()
|
||
|
if hasattr(content, "__methods__"):
|
||
|
keylist = keylist + content.__methods__
|
||
|
if hasattr(content, "__members__"):
|
||
|
keylist = keylist + content.__members__
|
||
|
if hasattr(content, "__class__"):
|
||
|
keylist.append("__class__")
|
||
|
if hasattr(content, "__bases__"):
|
||
|
keylist.append("__bases__")
|
||
|
if hasattr(content, "__name__"):
|
||
|
title = content.__name__
|
||
|
if "__name__" not in keylist:
|
||
|
keylist.append("__name__")
|
||
|
keylist.sort()
|
||
|
for key in keylist:
|
||
|
valuelist.append(getattr(content, key))
|
||
|
if content <> None:
|
||
|
title = title + "\r" + cleantype(content)
|
||
|
# now make that list!
|
||
|
tp, h, rect = self.wid.GetDialogItem(self.itemnum - 1)
|
||
|
Dlg.SetDialogItemText(h, title[:255])
|
||
|
self.list.LDelRow(0, 1)
|
||
|
self.list.LSetDrawingMode(0)
|
||
|
self.list.LAddRow(len(keylist), 0)
|
||
|
for i in range(len(keylist)):
|
||
|
self.list.LSetCell(str(keylist[i]), (0, i))
|
||
|
self.list.LSetDrawingMode(1)
|
||
|
self.list.LUpdate(self.wid.GetWindowPort().visRgn)
|
||
|
self.content = content
|
||
|
self.keylist = keylist
|
||
|
self.valuelist = valuelist
|
||
|
self.title = title
|
||
|
|
||
|
# draw a frame around the list, List Manager doesn't do that
|
||
|
def drawframe(self):
|
||
|
Qd.SetPort(self.wid)
|
||
|
Qd.FrameRect(self.rect)
|
||
|
rect2 = Qd.InsetRect(self.rect, -3, -3)
|
||
|
save = Qd.GetPenState()
|
||
|
Qd.PenSize(2, 2)
|
||
|
if self.active:
|
||
|
Qd.PenPat(Qd.qd.black)
|
||
|
else:
|
||
|
Qd.PenPat(Qd.qd.white)
|
||
|
# draw (or erase) an extra frame to indicate this is the acive list (or not)
|
||
|
Qd.FrameRect(rect2)
|
||
|
Qd.SetPenState(save)
|
||
|
|
||
|
|
||
|
|
||
|
class VarBrowser(FrameWork.DialogWindow):
|
||
|
def open(self, start_object, title = ""):
|
||
|
FrameWork.DialogWindow.open(self, ID_MAIN)
|
||
|
if title <> "":
|
||
|
windowtitle = self.wid.GetWTitle()
|
||
|
self.wid.SetWTitle(windowtitle + " >> " + title)
|
||
|
else:
|
||
|
if hasattr(start_object, "__name__"):
|
||
|
windowtitle = self.wid.GetWTitle()
|
||
|
self.wid.SetWTitle(windowtitle + " >> " + str(getattr(start_object, "__name__")) )
|
||
|
|
||
|
self.SetPort()
|
||
|
Qd.TextFont(3)
|
||
|
Qd.TextSize(9)
|
||
|
self.lists = []
|
||
|
self.listitems = []
|
||
|
for i in range(NUM_LISTS):
|
||
|
self.listitems.append(MAIN_LIST + 2 * i) # dlg item numbers... have to be consistent
|
||
|
for i in self.listitems:
|
||
|
tp, h, rect = self.wid.GetDialogItem(i)
|
||
|
list = MyList(self.wid, rect, i)
|
||
|
self.lists.append(list)
|
||
|
self.leftover = []
|
||
|
self.rightover = []
|
||
|
self.setup(start_object, title)
|
||
|
|
||
|
def close(self):
|
||
|
self.lists = []
|
||
|
self.listitems = []
|
||
|
self.do_postclose()
|
||
|
|
||
|
def setup(self, start_object, title = ""):
|
||
|
# here we set the starting point for our expedition
|
||
|
self.start = start_object
|
||
|
self.lists[0].setcontent(start_object, title)
|
||
|
for list in self.lists[1:]:
|
||
|
list.setcontent(None)
|
||
|
|
||
|
def do_listhit(self, event, item):
|
||
|
(what, message, when, where, modifiers) = event
|
||
|
Qd.SetPort(self.wid)
|
||
|
where = Qd.GlobalToLocal(where)
|
||
|
for list in self.lists:
|
||
|
list.active = 0
|
||
|
list = self.lists[self.listitems.index(item)]
|
||
|
list.active = 1
|
||
|
for l in self.lists:
|
||
|
l.drawframe()
|
||
|
|
||
|
point = (0,0)
|
||
|
ok, point = list.list.LGetSelect(1, point)
|
||
|
if ok:
|
||
|
oldsel = point[1]
|
||
|
else:
|
||
|
oldsel = -1
|
||
|
# This should be: list.list.LClick(where, modifiers)
|
||
|
# Since the selFlags field of the list is not accessible from Python I have to do it like this.
|
||
|
# The effect is that you can't select more items by using shift or command.
|
||
|
list.list.LClick(where, 0)
|
||
|
|
||
|
index = self.listitems.index(item) + 1
|
||
|
point = (0,0)
|
||
|
ok, point = list.list.LGetSelect(1, point)
|
||
|
if oldsel == point[1]:
|
||
|
return # selection didn't change, do nothing.
|
||
|
if not ok:
|
||
|
for i in range(index, len(self.listitems)):
|
||
|
self.lists[i].setcontent(None)
|
||
|
self.rightover = []
|
||
|
return
|
||
|
|
||
|
if point[1] >= len(list.keylist):
|
||
|
return # XXX is this still necessary? is ok really true?
|
||
|
key = str(list.keylist[point[1]])
|
||
|
value = list.valuelist[point[1]]
|
||
|
|
||
|
self.settextitem("")
|
||
|
thetype = type(value)
|
||
|
if thetype == ListType or \
|
||
|
thetype == TupleType or \
|
||
|
thetype == DictType or \
|
||
|
hasattr(value, "__dict__") or \
|
||
|
hasattr(value, "__methods__") or \
|
||
|
hasattr(value, "__members__"): # XXX or, or... again: did I miss something?
|
||
|
if index >= len(self.listitems):
|
||
|
# we've reached the right side of our dialog. move everything to the left
|
||
|
# (by pushing the rightbutton...)
|
||
|
self.do_rightbutton(1)
|
||
|
index = index - 1
|
||
|
newlist = self.lists[index]
|
||
|
newlist.setcontent(value, key)
|
||
|
else:
|
||
|
index = index - 1
|
||
|
self.settextitem( str(value) + "\r" + cleantype(value))
|
||
|
for i in range(index + 1, len(self.listitems)):
|
||
|
self.lists[i].setcontent(None)
|
||
|
self.rightover = []
|
||
|
|
||
|
# helper to set the big text item at the bottom of the dialog.
|
||
|
def settextitem(self, text):
|
||
|
tp, h, rect = self.wid.GetDialogItem(MAIN_TEXT)
|
||
|
Dlg.SetDialogItemText(h, text[:255])
|
||
|
|
||
|
def do_rawupdate(self, window, event):
|
||
|
Qd.SetPort(self.wid)
|
||
|
iType, iHandle, iRect = window.GetDialogItem(MAIN_LINE)
|
||
|
Qd.FrameRect(iRect)
|
||
|
for list in self.lists:
|
||
|
Qd.FrameRect(list.rect)
|
||
|
if list.active:
|
||
|
# see MyList.drawframe
|
||
|
rect2 = Qd.InsetRect(list.rect, -3, -3)
|
||
|
save = Qd.GetPenState()
|
||
|
Qd.PenSize(2, 2)
|
||
|
Qd.FrameRect(rect2)
|
||
|
Qd.SetPenState(save)
|
||
|
for list in self.lists:
|
||
|
list.list.LUpdate(self.wid.GetWindowPort().visRgn)
|
||
|
|
||
|
def do_activate(self, activate, event):
|
||
|
for list in self.lists:
|
||
|
list.list.LActivate(activate)
|
||
|
|
||
|
# scroll everything one 'unit' to the left
|
||
|
# XXX I don't like the way this works. Too many 'manual' assignments
|
||
|
def do_rightbutton(self, force = 0):
|
||
|
if not force and self.rightover == []:
|
||
|
return
|
||
|
self.scroll(-1)
|
||
|
point = (0, 0)
|
||
|
ok, point = self.lists[0].list.LGetSelect(1, point)
|
||
|
self.leftover.append(point, self.lists[0].content, self.lists[0].title, self.lists[0].active)
|
||
|
for i in range(len(self.lists)-1):
|
||
|
point = (0, 0)
|
||
|
ok, point = self.lists[i+1].list.LGetSelect(1, point)
|
||
|
self.lists[i].setcontent(self.lists[i+1].content, self.lists[i+1].title)
|
||
|
self.lists[i].list.LSetSelect(ok, point)
|
||
|
self.lists[i].list.LAutoScroll()
|
||
|
self.lists[i].active = self.lists[i+1].active
|
||
|
self.lists[i].drawframe()
|
||
|
if len(self.rightover) > 0:
|
||
|
point, content, title, active = self.rightover[-1]
|
||
|
self.lists[-1].setcontent(content, title)
|
||
|
self.lists[-1].list.LSetSelect(1, point)
|
||
|
self.lists[-1].list.LAutoScroll()
|
||
|
self.lists[-1].active = active
|
||
|
self.lists[-1].drawframe()
|
||
|
del self.rightover[-1]
|
||
|
else:
|
||
|
self.lists[-1].setcontent(None)
|
||
|
self.lists[-1].active = 0
|
||
|
for list in self.lists:
|
||
|
list.drawframe()
|
||
|
|
||
|
# scroll everything one 'unit' to the right
|
||
|
def do_leftbutton(self):
|
||
|
if self.leftover == []:
|
||
|
return
|
||
|
self.scroll(1)
|
||
|
if self.lists[-1].content <> None:
|
||
|
point = (0, 0)
|
||
|
ok, point = self.lists[-1].list.LGetSelect(1, point)
|
||
|
self.rightover.append(point, self.lists[-1].content, self.lists[-1].title, self.lists[-1].active )
|
||
|
for i in range(len(self.lists)-1, 0, -1):
|
||
|
point = (0, 0)
|
||
|
ok, point = self.lists[i-1].list.LGetSelect(1, point)
|
||
|
self.lists[i].setcontent(self.lists[i-1].content, self.lists[i-1].title)
|
||
|
self.lists[i].list.LSetSelect(ok, point)
|
||
|
self.lists[i].list.LAutoScroll()
|
||
|
self.lists[i].active = self.lists[i-1].active
|
||
|
self.lists[i].drawframe()
|
||
|
if len(self.leftover) > 0:
|
||
|
point, content, title, active = self.leftover[-1]
|
||
|
self.lists[0].setcontent(content, title)
|
||
|
self.lists[0].list.LSetSelect(1, point)
|
||
|
self.lists[0].list.LAutoScroll()
|
||
|
self.lists[0].active = active
|
||
|
self.lists[0].drawframe()
|
||
|
del self.leftover[-1]
|
||
|
else:
|
||
|
self.lists[0].setcontent(None)
|
||
|
self.lists[0].active = 0
|
||
|
|
||
|
# create some visual feedback when 'scrolling' the lists to the left or to the right
|
||
|
def scroll(self, leftright): # leftright should be 1 or -1
|
||
|
# first, build a region containing all list rectangles
|
||
|
myregion = Qd.NewRgn()
|
||
|
mylastregion = Qd.NewRgn()
|
||
|
for list in self.lists:
|
||
|
AddRect2Rgn(list.rect, myregion)
|
||
|
AddRect2Rgn(list.rect, mylastregion)
|
||
|
# set the pen, but save it's state first
|
||
|
self.SetPort()
|
||
|
save = Qd.GetPenState()
|
||
|
Qd.PenPat(Qd.qd.gray)
|
||
|
Qd.PenMode(srcXor)
|
||
|
# how far do we have to scroll?
|
||
|
distance = self.lists[1].rect[0] - self.lists[0].rect[0]
|
||
|
step = 30
|
||
|
lasttime = time.clock() # for delay
|
||
|
# do it
|
||
|
for i in range(0, distance, step):
|
||
|
if i <> 0:
|
||
|
Qd.FrameRgn(mylastregion) # erase last region
|
||
|
Qd.OffsetRgn(mylastregion, step * leftright, 0)
|
||
|
# draw gray region
|
||
|
Qd.FrameRgn(myregion)
|
||
|
Qd.OffsetRgn(myregion, step * leftright, 0)
|
||
|
while time.clock() - lasttime < 0.05:
|
||
|
pass # delay
|
||
|
lasttime = time.clock()
|
||
|
# clean up after your dog
|
||
|
Qd.FrameRgn(mylastregion)
|
||
|
Qd.SetPenState(save)
|
||
|
|
||
|
def reset(self):
|
||
|
for list in self.lists:
|
||
|
point = (0,0)
|
||
|
ok, point = list.list.LGetSelect(1, point)
|
||
|
if ok:
|
||
|
sel = list.keylist[point[1]]
|
||
|
list.setcontent(list.content, list.title)
|
||
|
if ok:
|
||
|
list.list.LSetSelect(1, (0, list.keylist.index(sel)))
|
||
|
list.list.LAutoScroll()
|
||
|
|
||
|
def do_itemhit(self, item, event):
|
||
|
if item in self.listitems:
|
||
|
self.do_listhit(event, item)
|
||
|
elif item == MAIN_LEFT:
|
||
|
self.do_leftbutton()
|
||
|
elif item == MAIN_RIGHT:
|
||
|
self.do_rightbutton()
|
||
|
elif item == MAIN_CLOSE:
|
||
|
self.close()
|
||
|
elif item == MAIN_RESET:
|
||
|
self.reset()
|
||
|
|
||
|
# helper function that returns a short string containing the type of an arbitrary object
|
||
|
# eg: cleantype("wat is dit nu weer?") -> '(string)'
|
||
|
def cleantype(obj):
|
||
|
# type() typically returns something like: <type 'string'>
|
||
|
items = string.split(str(type(obj)), "'")
|
||
|
if len(items) == 3:
|
||
|
return '(' + items[1] + ')'
|
||
|
else:
|
||
|
# just in case, I don't know.
|
||
|
return str(type(obj))
|
||
|
|
||
|
# helper for VarBrowser.scroll
|
||
|
def AddRect2Rgn(theRect, theRgn):
|
||
|
rRgn = Qd.NewRgn()
|
||
|
Qd.RectRgn(rRgn, theRect)
|
||
|
Qd.UnionRgn(rRgn, theRgn, theRgn)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|