| 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]) |
| 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 = ( |
| 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) |
| |