mirror of
https://github.com/AdaCore/cpython.git
synced 2026-02-12 12:57:15 -08:00
Make object browser work in OSX (by rewriting the old browser LDEF in Python). If at all possible, this should go into 2.2.1. Use the Carbon scrap manager interface if the old interface isn't available.
598 lines
16 KiB
Python
598 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.List import GetListPort
|
|
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(l + 2, 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.qd.black)
|
|
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):
|
|
import macfs
|
|
fss = macfs.FSSpec(value)
|
|
if fss.GetCreatorType()[1] == 'TEXT':
|
|
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(GetListPort(theList))
|
|
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])
|
|
key, value = data[6:].split("\t", 1)
|
|
|
|
if iconId:
|
|
theIcon = Icn.GetCIcon(iconId)
|
|
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)
|
|
|
|
# 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 = (
|
|
types.NoneType,
|
|
types.IntType,
|
|
types.LongType,
|
|
types.FloatType,
|
|
types.ComplexType,
|
|
types.StringType
|
|
)
|
|
|
|
INDEXING_TYPES = (
|
|
types.TupleType,
|
|
types.ListType,
|
|
types.DictionaryType
|
|
)
|
|
|
|
def unpack_object(object, indent = 0):
|
|
tp = type(object)
|
|
if tp in SIMPLE_TYPES and tp is not types.NoneType:
|
|
raise TypeError, "can't browse simple type: %s" % tp.__name__
|
|
elif tp == types.DictionaryType:
|
|
return unpack_dict(object, indent)
|
|
elif tp in (types.TupleType, types.ListType):
|
|
return unpack_sequence(object, indent)
|
|
elif tp == types.InstanceType:
|
|
return unpack_instance(object, indent)
|
|
elif tp == types.ClassType:
|
|
return unpack_class(object, indent)
|
|
elif tp == types.ModuleType:
|
|
return unpack_dict(object.__dict__, indent)
|
|
else:
|
|
return unpack_other(object, indent)
|
|
|
|
def unpack_sequence(seq, indent = 0):
|
|
items = map(None, range(len(seq)), seq)
|
|
items = map(lambda (k, v), type = type, simp = SIMPLE_TYPES, indent = indent:
|
|
(k, v, not type(v) in simp, indent), items)
|
|
return items
|
|
|
|
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 = []
|
|
if hasattr(object, '__members__'):
|
|
attrs = attrs + object.__members__
|
|
if hasattr(object, '__methods__'):
|
|
attrs = attrs + object.__methods__
|
|
if hasattr(object, '__dict__'):
|
|
attrs = attrs + object.__dict__.keys()
|
|
if hasattr(object, '__slots__'):
|
|
# XXX??
|
|
attrs = attrs + object.__slots__
|
|
if hasattr(object, "__class__") and "__class__" not in attrs:
|
|
attrs.append("__class__")
|
|
if hasattr(object, "__doc__") and "__doc__" not in attrs:
|
|
attrs.append("__doc__")
|
|
items = []
|
|
for attr in attrs:
|
|
items.append((attr, getattr(object, attr)))
|
|
return pack_items(items, indent)
|
|
|
|
def pack_items(items, indent = 0):
|
|
items = map(lambda (k, v), type = type, simp = SIMPLE_TYPES, indent = indent:
|
|
(k, v, not type(v) in simp, indent),
|
|
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)
|
|
|