cpython/Mac/Tools/IDE/PyBrowser.py

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)