618 lines
16 KiB
Python
618 lines
16 KiB
Python
import W
|
|
import Wkeys
|
|
import struct
|
|
import string
|
|
import types
|
|
import re
|
|
from Carbon import Qd, Icn, Fm, QuickDraw
|
|
from Carbon.QuickDraw import hilitetransfermode
|
|
|
|
|
|
nullid = '\0\0'
|
|
closedid = struct.pack('h', 468)
|
|
openid = struct.pack('h', 469)
|
|
closedsolidid = struct.pack('h', 470)
|
|
opensolidid = struct.pack('h', 471)
|
|
|
|
arrows = (nullid, closedid, openid, closedsolidid, opensolidid)
|
|
|
|
has_ctlcharsRE = re.compile(r'[\000-\037\177-\377]')
|
|
def ctlcharsREsearch(str):
|
|
if has_ctlcharsRE.search(str) is None:
|
|
return -1
|
|
return 1
|
|
|
|
def double_repr(key, value, truncvalue = 0,
|
|
type = type, StringType = types.StringType,
|
|
has_ctlchars = ctlcharsREsearch, _repr = repr, str = str):
|
|
if type(key) == StringType and has_ctlchars(key) < 0:
|
|
key = str(key)
|
|
else:
|
|
key = _repr(key)
|
|
if key == '__builtins__':
|
|
value = "<" + type(value).__name__ + " '__builtin__'>"
|
|
elif key == '__return__':
|
|
# bleh, when returning from a class codeblock we get infinite recursion in repr.
|
|
# Use safe repr instead.
|
|
import repr
|
|
value = repr.repr(value)
|
|
else:
|
|
try:
|
|
value = _repr(value)
|
|
'' + value # test to see if it is a string, in case a __repr__ method is buggy
|
|
except:
|
|
value = '\xa5\xa5\xa5 exception in repr()'
|
|
if truncvalue:
|
|
return key + '\t' + value[:255]
|
|
return key + '\t' + value
|
|
|
|
|
|
def truncString(s, maxwid):
|
|
if maxwid < 1:
|
|
return 1, ""
|
|
strlen = len(s)
|
|
strwid = Qd.TextWidth(s, 0, strlen);
|
|
if strwid <= maxwid:
|
|
return 0, s
|
|
|
|
Qd.TextFace(QuickDraw.condense)
|
|
strwid = Qd.TextWidth(s, 0, strlen)
|
|
ellipsis = Qd.StringWidth('\xc9')
|
|
|
|
if strwid <= maxwid:
|
|
Qd.TextFace(0)
|
|
return 1, s
|
|
if strwid < 1:
|
|
Qd.TextFace(0)
|
|
return 1, ""
|
|
|
|
mid = int(strlen * maxwid / strwid)
|
|
while 1:
|
|
if mid <= 0:
|
|
mid = 0
|
|
break
|
|
strwid = Qd.TextWidth(s, 0, mid) + ellipsis
|
|
strwid2 = Qd.TextWidth(s, 0, mid + 1) + ellipsis
|
|
if strwid <= maxwid and maxwid <= strwid2:
|
|
if maxwid == strwid2:
|
|
mid += 1
|
|
break
|
|
if strwid > maxwid:
|
|
mid -= 1
|
|
if mid <= 0:
|
|
mid = 0
|
|
break
|
|
elif strwid2 < maxwid:
|
|
mid += 1
|
|
Qd.TextFace(0)
|
|
return 1, s[:mid] + '\xc9'
|
|
|
|
|
|
def drawTextCell(text, cellRect, ascent, theList):
|
|
l, t, r, b = cellRect
|
|
cellwidth = r - l
|
|
Qd.MoveTo(int(l + 2), int(t + ascent))
|
|
condense, text = truncString(text, cellwidth - 3)
|
|
if condense:
|
|
Qd.TextFace(QuickDraw.condense)
|
|
Qd.DrawText(text, 0, len(text))
|
|
Qd.TextFace(0)
|
|
|
|
|
|
PICTWIDTH = 16
|
|
|
|
|
|
class BrowserWidget(W.CustomList):
|
|
|
|
def __init__(self, possize, object = None, col = 100, closechildren = 0):
|
|
W.List.__init__(self, possize, callback = self.listhit)
|
|
self.object = (None,)
|
|
self.indent = 16
|
|
self.lastmaxindent = 0
|
|
self.closechildren = closechildren
|
|
self.children = []
|
|
self.mincol = 64
|
|
self.setcolumn(col)
|
|
self.bind('return', self.openselection)
|
|
self.bind('enter', self.openselection)
|
|
if object is not None:
|
|
self.set(object)
|
|
|
|
def set(self, object):
|
|
if self.object[0] is not object:
|
|
self.object = object,
|
|
self[:] = self.unpack(object, 0)
|
|
elif self._parentwindow is not None and self._parentwindow.wid:
|
|
self.update()
|
|
|
|
def unpack(self, object, indent):
|
|
return unpack_object(object, indent)
|
|
|
|
def update(self):
|
|
# for now...
|
|
W.SetCursor('watch')
|
|
self.setdrawingmode(0)
|
|
sel = self.getselectedobjects()
|
|
fold = self.getunfoldedobjects()
|
|
topcell = self.gettopcell()
|
|
self[:] = self.unpack(self.object[0], 0)
|
|
self.unfoldobjects(fold)
|
|
self.setselectedobjects(sel)
|
|
self.settopcell(topcell)
|
|
self.setdrawingmode(1)
|
|
|
|
def setcolumn(self, col):
|
|
self.col = col
|
|
self.colstr = struct.pack('h', col)
|
|
if self._list:
|
|
sel = self.getselection()
|
|
self.setitems(self.items)
|
|
self.setselection(sel)
|
|
|
|
def key(self, char, event):
|
|
if char in (Wkeys.leftarrowkey, Wkeys.rightarrowkey):
|
|
sel = self.getselection()
|
|
sel.reverse()
|
|
self.setdrawingmode(0)
|
|
for index in sel:
|
|
self.fold(index, char == Wkeys.rightarrowkey)
|
|
self.setdrawingmode(1)
|
|
else:
|
|
W.List.key(self, char, event)
|
|
|
|
def rollover(self, (x, y), onoff):
|
|
if onoff:
|
|
if self.incolumn((x, y)):
|
|
W.SetCursor('hmover')
|
|
else:
|
|
W.SetCursor('arrow')
|
|
|
|
def inarrow(self, (x, y)):
|
|
cl, ct, cr, cb = self._list.LRect((0, 0))
|
|
l, t, r, b = self._bounds
|
|
if (x - cl) < 16:
|
|
cellheight = cb - ct
|
|
index = (y - ct) / cellheight
|
|
if index < len(self.items):
|
|
return 1, index
|
|
return None, None
|
|
|
|
def incolumn(self, (x, y)):
|
|
l, t, r, b = self._list.LRect((0, 0))
|
|
abscol = l + self.col
|
|
return abs(abscol - x) < 3
|
|
|
|
def trackcolumn(self, (x, y)):
|
|
from Carbon import Qd, QuickDraw, Evt
|
|
self.SetPort()
|
|
l, t, r, b = self._bounds
|
|
bounds = l, t, r, b = l + 1, t + 1, r - 16, b - 1
|
|
abscol = l + self.col
|
|
mincol = l + self.mincol
|
|
maxcol = r - 10
|
|
diff = abscol - x
|
|
Qd.PenPat('\000\377\000\377\000\377\000\377')
|
|
Qd.PenMode(QuickDraw.srcXor)
|
|
rect = abscol - 1, t, abscol, b
|
|
Qd.PaintRect(rect)
|
|
lastpoint = (x, y)
|
|
newcol = -1
|
|
#W.SetCursor('fist')
|
|
while Evt.Button():
|
|
Evt.WaitNextEvent(0, 1, None) # needed for OSX
|
|
(x, y) = Evt.GetMouse()
|
|
if (x, y) <> lastpoint:
|
|
newcol = x + diff
|
|
newcol = max(newcol, mincol)
|
|
newcol = min(newcol, maxcol)
|
|
Qd.PaintRect(rect)
|
|
rect = newcol - 1, t, newcol, b
|
|
Qd.PaintRect(rect)
|
|
lastpoint = (x, y)
|
|
Qd.PaintRect(rect)
|
|
Qd.PenPat(Qd.GetQDGlobalsBlack())
|
|
Qd.PenNormal()
|
|
if newcol > 0 and newcol <> abscol:
|
|
self.setcolumn(newcol - l)
|
|
|
|
def click(self, point, modifiers):
|
|
if point == (-1, -1): # gross.
|
|
W.List.click(self, point ,modifiers)
|
|
return
|
|
hit, index = self.inarrow(point)
|
|
if hit:
|
|
(key, value, arrow, indent) = self.items[index]
|
|
self.fold(index, arrow == 1)
|
|
elif self.incolumn(point):
|
|
self.trackcolumn(point)
|
|
else:
|
|
W.List.click(self, point, modifiers)
|
|
|
|
# for W.List.key
|
|
def findmatch(self, tag):
|
|
lower = string.lower
|
|
items = self.items
|
|
taglen = len(tag)
|
|
match = '\377' * 100
|
|
match_i = -1
|
|
for i in range(len(items)):
|
|
item = lower(str(items[i][0]))
|
|
if tag <= item < match:
|
|
match = item
|
|
match_i = i
|
|
if match_i >= 0:
|
|
return match_i
|
|
else:
|
|
return len(items) - 1
|
|
|
|
def close(self):
|
|
if self.closechildren:
|
|
for window in self.children:
|
|
window.close()
|
|
self.children = []
|
|
W.List.close(self)
|
|
|
|
def fold(self, index, onoff):
|
|
(key, value, arrow, indent) = self.items[index]
|
|
if arrow == 0 or (onoff and arrow == 2) or (not onoff and arrow == 1):
|
|
return
|
|
W.SetCursor('watch')
|
|
topcell = self.gettopcell()
|
|
if onoff:
|
|
self[index] = (key, value, 4, indent)
|
|
self.setdrawingmode(0)
|
|
self[index+1:index+1] = self.unpack(value, indent + 1)
|
|
self[index] = (key, value, 2, indent)
|
|
else:
|
|
self[index] = (key, value, 3, indent)
|
|
self.setdrawingmode(0)
|
|
count = 0
|
|
for i in range(index + 1, len(self.items)):
|
|
(dummy, dummy, dummy, subindent) = self.items[i]
|
|
if subindent <= indent:
|
|
break
|
|
count = count + 1
|
|
self[index+1:index+1+count] = []
|
|
self[index] = (key, value, 1, indent)
|
|
maxindent = self.getmaxindent()
|
|
if maxindent <> self.lastmaxindent:
|
|
newabsindent = self.col + (maxindent - self.lastmaxindent) * self.indent
|
|
if newabsindent >= self.mincol:
|
|
self.setcolumn(newabsindent)
|
|
self.lastmaxindent = maxindent
|
|
self.settopcell(topcell)
|
|
self.setdrawingmode(1)
|
|
|
|
def unfoldobjects(self, objects):
|
|
for obj in objects:
|
|
try:
|
|
index = self.items.index(obj)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
self.fold(index, 1)
|
|
|
|
def getunfoldedobjects(self):
|
|
curindent = 0
|
|
objects = []
|
|
for index in range(len(self.items)):
|
|
(key, value, arrow, indent) = self.items[index]
|
|
if indent > curindent:
|
|
(k, v, a, i) = self.items[index - 1]
|
|
objects.append((k, v, 1, i))
|
|
curindent = indent
|
|
elif indent < curindent:
|
|
curindent = indent
|
|
return objects
|
|
|
|
def listhit(self, isdbl):
|
|
if isdbl:
|
|
self.openselection()
|
|
|
|
def openselection(self):
|
|
import os
|
|
sel = self.getselection()
|
|
for index in sel:
|
|
(key, value, arrow, indent) = self[index]
|
|
if arrow:
|
|
self.children.append(Browser(value))
|
|
elif type(value) == types.StringType and '\0' not in value:
|
|
editor = self._parentwindow.parent.getscript(value)
|
|
if editor:
|
|
editor.select()
|
|
return
|
|
elif os.path.exists(value) and os.path.isfile(value):
|
|
if MacOS.GetCreatorAndType(value)[1] in ('TEXT', '\0\0\0\0'):
|
|
W.getapplication().openscript(value)
|
|
|
|
def itemrepr(self, (key, value, arrow, indent), str = str, double_repr = double_repr,
|
|
arrows = arrows, pack = struct.pack):
|
|
arrow = arrows[arrow]
|
|
return arrow + pack('h', self.indent * indent) + self.colstr + \
|
|
double_repr(key, value, 1)
|
|
|
|
def getmaxindent(self, max = max):
|
|
maxindent = 0
|
|
for item in self.items:
|
|
maxindent = max(maxindent, item[3])
|
|
return maxindent
|
|
|
|
def domenu_copy(self, *args):
|
|
sel = self.getselectedobjects()
|
|
selitems = []
|
|
for key, value, dummy, dummy in sel:
|
|
selitems.append(double_repr(key, value))
|
|
text = string.join(selitems, '\r')
|
|
if text:
|
|
from Carbon import Scrap
|
|
if hasattr(Scrap, 'PutScrap'):
|
|
Scrap.ZeroScrap()
|
|
Scrap.PutScrap('TEXT', text)
|
|
else:
|
|
Scrap.ClearCurrentScrap()
|
|
sc = Scrap.GetCurrentScrap()
|
|
sc.PutScrapFlavor('TEXT', 0, text)
|
|
|
|
def listDefDraw(self, selected, cellRect, theCell,
|
|
dataOffset, dataLen, theList):
|
|
self.myDrawCell(0, selected, cellRect, theCell,
|
|
dataOffset, dataLen, theList)
|
|
|
|
def listDefHighlight(self, selected, cellRect, theCell,
|
|
dataOffset, dataLen, theList):
|
|
self.myDrawCell(1, selected, cellRect, theCell,
|
|
dataOffset, dataLen, theList)
|
|
|
|
def myDrawCell(self, onlyHilite, selected, cellRect, theCell,
|
|
dataOffset, dataLen, theList):
|
|
savedPort = Qd.GetPort()
|
|
Qd.SetPort(theList.GetListPort())
|
|
savedClip = Qd.NewRgn()
|
|
Qd.GetClip(savedClip)
|
|
Qd.ClipRect(cellRect)
|
|
savedPenState = Qd.GetPenState()
|
|
Qd.PenNormal()
|
|
|
|
l, t, r, b = cellRect
|
|
|
|
if not onlyHilite:
|
|
Qd.EraseRect(cellRect)
|
|
|
|
ascent, descent, leading, size, hm = Fm.FontMetrics()
|
|
linefeed = ascent + descent + leading
|
|
|
|
if dataLen >= 6:
|
|
data = theList.LGetCell(dataLen, theCell)
|
|
iconId, indent, tab = struct.unpack("hhh", data[:6])
|
|
try:
|
|
key, value = data[6:].split("\t", 1)
|
|
except ValueError:
|
|
# bogus data, at least don't crash.
|
|
indent = 0
|
|
tab = 0
|
|
iconId = 0
|
|
key = ""
|
|
value = data[6:]
|
|
|
|
if iconId:
|
|
try:
|
|
theIcon = Icn.GetCIcon(iconId)
|
|
except Icn.Error:
|
|
pass
|
|
else:
|
|
rect = (0, 0, 16, 16)
|
|
rect = Qd.OffsetRect(rect, l, t)
|
|
rect = Qd.OffsetRect(rect, 0, (theList.cellSize[1] - (rect[3] - rect[1])) / 2)
|
|
Icn.PlotCIcon(rect, theIcon)
|
|
|
|
if len(key) >= 0:
|
|
cl, ct, cr, cb = cellRect
|
|
vl, vt, vr, vb = self._viewbounds
|
|
cl = vl + PICTWIDTH + indent
|
|
cr = vl + tab
|
|
if cr > vr:
|
|
cr = vr
|
|
if cl < cr:
|
|
drawTextCell(key, (cl, ct, cr, cb), ascent, theList)
|
|
cl = vl + tab
|
|
cr = vr
|
|
if cl < cr:
|
|
drawTextCell(value, (cl, ct, cr, cb), ascent, theList)
|
|
#elif dataLen != 0:
|
|
# drawTextCell("???", 3, cellRect, ascent, theList)
|
|
else:
|
|
return # we have bogus data
|
|
|
|
# draw nice dotted line
|
|
l, t, r, b = cellRect
|
|
l = self._viewbounds[0] + tab
|
|
r = l + 1;
|
|
if not (theList.cellSize[1] & 0x01) or (t & 0x01):
|
|
myPat = "\xff\x00\xff\x00\xff\x00\xff\x00"
|
|
else:
|
|
myPat = "\x00\xff\x00\xff\x00\xff\x00\xff"
|
|
Qd.PenPat(myPat)
|
|
Qd.PenMode(QuickDraw.srcCopy)
|
|
Qd.PaintRect((l, t, r, b))
|
|
Qd.PenNormal()
|
|
|
|
if selected or onlyHilite:
|
|
l, t, r, b = cellRect
|
|
l = self._viewbounds[0] + PICTWIDTH
|
|
r = self._viewbounds[2]
|
|
Qd.PenMode(hilitetransfermode)
|
|
Qd.PaintRect((l, t, r, b))
|
|
|
|
# restore graphics environment
|
|
Qd.SetPort(savedPort)
|
|
Qd.SetClip(savedClip)
|
|
Qd.DisposeRgn(savedClip)
|
|
Qd.SetPenState(savedPenState)
|
|
|
|
|
|
|
|
class Browser:
|
|
|
|
def __init__(self, object = None, title = None, closechildren = 0):
|
|
if hasattr(object, '__name__'):
|
|
name = object.__name__
|
|
else:
|
|
name = ''
|
|
if title is None:
|
|
title = 'Object browser'
|
|
if name:
|
|
title = title + ': ' + name
|
|
self.w = w = W.Window((300, 400), title, minsize = (100, 100))
|
|
w.info = W.TextBox((18, 8, -70, 15))
|
|
w.updatebutton = W.BevelButton((-64, 4, 50, 16), 'Update', self.update)
|
|
w.browser = BrowserWidget((-1, 24, 1, -14), None)
|
|
w.bind('cmdu', w.updatebutton.push)
|
|
w.open()
|
|
self.set(object, name)
|
|
|
|
def close(self):
|
|
if self.w.wid:
|
|
self.w.close()
|
|
|
|
def set(self, object, name = ''):
|
|
W.SetCursor('watch')
|
|
tp = type(object).__name__
|
|
try:
|
|
length = len(object)
|
|
except:
|
|
length = -1
|
|
if not name and hasattr(object, '__name__'):
|
|
name = object.__name__
|
|
if name:
|
|
info = name + ': ' + tp
|
|
else:
|
|
info = tp
|
|
if length >= 0:
|
|
if length == 1:
|
|
info = info + ' (%d element)' % length
|
|
else:
|
|
info = info + ' (%d elements)' % length
|
|
self.w.info.set(info)
|
|
self.w.browser.set(object)
|
|
|
|
def update(self):
|
|
self.w.browser.update()
|
|
|
|
|
|
SIMPLE_TYPES = (
|
|
type(None),
|
|
int,
|
|
long,
|
|
float,
|
|
complex,
|
|
str,
|
|
unicode,
|
|
)
|
|
|
|
def get_ivars(obj):
|
|
"""Return a list the names of all (potential) instance variables."""
|
|
# __mro__ recipe from Guido
|
|
slots = {}
|
|
# old-style C objects
|
|
if hasattr(obj, "__members__"):
|
|
for name in obj.__members__:
|
|
slots[name] = None
|
|
if hasattr(obj, "__methods__"):
|
|
for name in obj.__methods__:
|
|
slots[name] = None
|
|
# generic type
|
|
if hasattr(obj, "__dict__"):
|
|
slots.update(obj.__dict__)
|
|
cls = type(obj)
|
|
if hasattr(cls, "__mro__"):
|
|
# new-style class, use descriptors
|
|
for base in cls.__mro__:
|
|
for name, value in base.__dict__.items():
|
|
# XXX using callable() is a heuristic which isn't 100%
|
|
# foolproof.
|
|
if hasattr(value, "__get__") and not callable(value):
|
|
slots[name] = None
|
|
if "__dict__" in slots:
|
|
del slots["__dict__"]
|
|
slots = slots.keys()
|
|
slots.sort()
|
|
return slots
|
|
|
|
def unpack_object(object, indent = 0):
|
|
tp = type(object)
|
|
if isinstance(object, SIMPLE_TYPES) and object is not None:
|
|
raise TypeError, "can't browse simple type: %s" % tp.__name__
|
|
elif isinstance(object, dict):
|
|
return unpack_dict(object, indent)
|
|
elif isinstance(object, (tuple, list)):
|
|
return unpack_sequence(object, indent)
|
|
elif isinstance(object, types.ModuleType):
|
|
return unpack_dict(object.__dict__, indent)
|
|
else:
|
|
return unpack_other(object, indent)
|
|
|
|
def unpack_sequence(seq, indent = 0):
|
|
return [(i, v, not isinstance(v, SIMPLE_TYPES), indent)
|
|
for i, v in enumerate(seq)]
|
|
|
|
def unpack_dict(dict, indent = 0):
|
|
items = dict.items()
|
|
return pack_items(items, indent)
|
|
|
|
def unpack_instance(inst, indent = 0):
|
|
if hasattr(inst, '__pybrowse_unpack__'):
|
|
return unpack_object(inst.__pybrowse_unpack__(), indent)
|
|
else:
|
|
items = [('__class__', inst.__class__)] + inst.__dict__.items()
|
|
return pack_items(items, indent)
|
|
|
|
def unpack_class(clss, indent = 0):
|
|
items = [('__bases__', clss.__bases__), ('__name__', clss.__name__)] + clss.__dict__.items()
|
|
return pack_items(items, indent)
|
|
|
|
def unpack_other(object, indent = 0):
|
|
attrs = get_ivars(object)
|
|
items = []
|
|
for attr in attrs:
|
|
try:
|
|
value = getattr(object, attr)
|
|
except:
|
|
pass
|
|
else:
|
|
items.append((attr, value))
|
|
return pack_items(items, indent)
|
|
|
|
def pack_items(items, indent = 0):
|
|
items = [(k, v, not isinstance(v, SIMPLE_TYPES), indent)
|
|
for k, v in items]
|
|
return tuple_caselesssort(items)
|
|
|
|
def caselesssort(alist):
|
|
"""Return a sorted copy of a list. If there are only strings in the list,
|
|
it will not consider case"""
|
|
|
|
try:
|
|
# turn ['FOO', 'aaBc', 'ABcD'] into [('foo', 'FOO'), ('aabc', 'aaBc'), ('abcd', 'ABcD')], if possible
|
|
tupledlist = map(lambda item, lower = string.lower: (lower(item), item), alist)
|
|
except TypeError:
|
|
# at least one element in alist is not a string, proceed the normal way...
|
|
alist = alist[:]
|
|
alist.sort()
|
|
return alist
|
|
else:
|
|
tupledlist.sort()
|
|
# turn [('aabc', 'aaBc'), ('abcd', 'ABcD'), ('foo', 'FOO')] into ['aaBc', 'ABcD', 'FOO']
|
|
return map(lambda x: x[1], tupledlist)
|
|
|
|
def tuple_caselesssort(items):
|
|
try:
|
|
tupledlist = map(lambda tuple, lower = string.lower: (lower(tuple[0]), tuple), items)
|
|
except (AttributeError, TypeError):
|
|
items = items[:]
|
|
items.sort()
|
|
return items
|
|
else:
|
|
tupledlist.sort()
|
|
return map(lambda (low, tuple): tuple, tupledlist)
|
|
|