blob: c1a3d055e24c10f48a230ff91dd5ab0598a92fc1 [file] [log] [blame]
Just van Rossum40f9b7b1999-01-30 22:39:17 +00001import W
2import Wkeys
3import struct
4import string
5import types
Jack Jansen9ad27522001-02-21 13:54:31 +00006import re
Just van Rossum4e6d13c2002-03-26 12:06:11 +00007from Carbon import Qd, Icn, Fm, QuickDraw
8from Carbon.List import GetListPort
9from Carbon.QuickDraw import hilitetransfermode
10
Just van Rossum40f9b7b1999-01-30 22:39:17 +000011
12nullid = '\0\0'
13closedid = struct.pack('h', 468)
14openid = struct.pack('h', 469)
15closedsolidid = struct.pack('h', 470)
16opensolidid = struct.pack('h', 471)
17
18arrows = (nullid, closedid, openid, closedsolidid, opensolidid)
19
Just van Rossum3eec7622001-07-10 19:25:40 +000020has_ctlcharsRE = re.compile(r'[\000-\037\177-\377]')
Jack Jansen9ad27522001-02-21 13:54:31 +000021def ctlcharsREsearch(str):
Jack Jansendbd0c3a2001-03-01 23:15:54 +000022 if has_ctlcharsRE.search(str) is None:
Jack Jansen9ad27522001-02-21 13:54:31 +000023 return -1
24 return 1
25
Just van Rossum40f9b7b1999-01-30 22:39:17 +000026def double_repr(key, value, truncvalue = 0,
27 type = type, StringType = types.StringType,
Jack Jansen9ad27522001-02-21 13:54:31 +000028 has_ctlchars = ctlcharsREsearch, _repr = repr, str = str):
Just van Rossum40f9b7b1999-01-30 22:39:17 +000029 if type(key) == StringType and has_ctlchars(key) < 0:
30 key = str(key)
31 else:
32 key = _repr(key)
Just van Rossum460ff201999-10-30 11:43:25 +000033 if key == '__builtins__':
Just van Rossum40f9b7b1999-01-30 22:39:17 +000034 value = "<" + type(value).__name__ + " '__builtin__'>"
35 elif key == '__return__':
36 # bleh, when returning from a class codeblock we get infinite recursion in repr.
37 # Use safe repr instead.
38 import repr
39 value = repr.repr(value)
40 else:
41 try:
42 value = _repr(value)
43 '' + value # test to see if it is a string, in case a __repr__ method is buggy
44 except:
Just van Rossumdc3c6172001-06-19 21:37:33 +000045 value = '\xa5\xa5\xa5 exception in repr()'
Just van Rossum40f9b7b1999-01-30 22:39:17 +000046 if truncvalue:
47 return key + '\t' + value[:255]
48 return key + '\t' + value
49
50
Just van Rossum4e6d13c2002-03-26 12:06:11 +000051def truncString(s, maxwid):
52 if maxwid < 1:
53 return 1, ""
54 strlen = len(s)
55 strwid = Qd.TextWidth(s, 0, strlen);
56 if strwid <= maxwid:
57 return 0, s
Just van Rossum40f9b7b1999-01-30 22:39:17 +000058
Just van Rossum4e6d13c2002-03-26 12:06:11 +000059 Qd.TextFace(QuickDraw.condense)
60 strwid = Qd.TextWidth(s, 0, strlen)
61 ellipsis = Qd.StringWidth('\xc9')
62
63 if strwid <= maxwid:
64 Qd.TextFace(0)
65 return 1, s
66 if strwid < 1:
67 Qd.TextFace(0)
68 return 1, ""
69
70 mid = int(strlen * maxwid / strwid)
71 while 1:
72 if mid <= 0:
73 mid = 0
74 break
75 strwid = Qd.TextWidth(s, 0, mid) + ellipsis
76 strwid2 = Qd.TextWidth(s, 0, mid + 1) + ellipsis
77 if strwid <= maxwid and maxwid <= strwid2:
78 if maxwid == strwid2:
79 mid += 1
80 break
81 if strwid > maxwid:
82 mid -= 1
83 if mid <= 0:
84 mid = 0
85 break
86 elif strwid2 < maxwid:
87 mid += 1
88 Qd.TextFace(0)
89 return 1, s[:mid] + '\xc9'
90
91
92def drawTextCell(text, cellRect, ascent, theList):
93 l, t, r, b = cellRect
94 cellwidth = r - l
95 Qd.MoveTo(l + 2, t + ascent)
96 condense, text = truncString(text, cellwidth - 3)
97 if condense:
98 Qd.TextFace(QuickDraw.condense)
99 Qd.DrawText(text, 0, len(text))
100 Qd.TextFace(0)
101
102
103PICTWIDTH = 16
104
105
106class BrowserWidget(W.CustomList):
Just van Rossum40f9b7b1999-01-30 22:39:17 +0000107
108 def __init__(self, possize, object = None, col = 100, closechildren = 0):
109 W.List.__init__(self, possize, callback = self.listhit)
110 self.object = (None,)
111 self.indent = 16
112 self.lastmaxindent = 0
113 self.closechildren = closechildren
114 self.children = []
115 self.mincol = 64
116 self.setcolumn(col)
117 self.bind('return', self.openselection)
118 self.bind('enter', self.openselection)
119 if object is not None:
120 self.set(object)
121
122 def set(self, object):
123 if self.object[0] is not object:
124 self.object = object,
125 self[:] = self.unpack(object, 0)
126 elif self._parentwindow is not None and self._parentwindow.wid:
127 self.update()
128
129 def unpack(self, object, indent):
130 return unpack_object(object, indent)
131
132 def update(self):
133 # for now...
134 W.SetCursor('watch')
135 self.setdrawingmode(0)
136 sel = self.getselectedobjects()
137 fold = self.getunfoldedobjects()
138 topcell = self.gettopcell()
139 self[:] = self.unpack(self.object[0], 0)
140 self.unfoldobjects(fold)
141 self.setselectedobjects(sel)
142 self.settopcell(topcell)
143 self.setdrawingmode(1)
144
145 def setcolumn(self, col):
146 self.col = col
147 self.colstr = struct.pack('h', col)
148 if self._list:
149 sel = self.getselection()
150 self.setitems(self.items)
151 self.setselection(sel)
152
153 def key(self, char, event):
154 if char in (Wkeys.leftarrowkey, Wkeys.rightarrowkey):
155 sel = self.getselection()
156 sel.reverse()
157 self.setdrawingmode(0)
158 for index in sel:
159 self.fold(index, char == Wkeys.rightarrowkey)
160 self.setdrawingmode(1)
161 else:
162 W.List.key(self, char, event)
163
164 def rollover(self, (x, y), onoff):
165 if onoff:
166 if self.incolumn((x, y)):
167 W.SetCursor('hmover')
168 else:
169 W.SetCursor('arrow')
170
171 def inarrow(self, (x, y)):
172 cl, ct, cr, cb = self._list.LRect((0, 0))
173 l, t, r, b = self._bounds
174 if (x - cl) < 16:
175 cellheight = cb - ct
176 index = (y - ct) / cellheight
177 if index < len(self.items):
178 return 1, index
179 return None, None
180
181 def incolumn(self, (x, y)):
182 l, t, r, b = self._list.LRect((0, 0))
183 abscol = l + self.col
184 return abs(abscol - x) < 3
185
186 def trackcolumn(self, (x, y)):
Jack Jansen5a6fdcd2001-08-25 12:15:04 +0000187 from Carbon import Qd, QuickDraw, Evt
Just van Rossum40f9b7b1999-01-30 22:39:17 +0000188 self.SetPort()
189 l, t, r, b = self._bounds
190 bounds = l, t, r, b = l + 1, t + 1, r - 16, b - 1
191 abscol = l + self.col
192 mincol = l + self.mincol
193 maxcol = r - 10
194 diff = abscol - x
195 Qd.PenPat('\000\377\000\377\000\377\000\377')
196 Qd.PenMode(QuickDraw.srcXor)
197 rect = abscol - 1, t, abscol, b
198 Qd.PaintRect(rect)
199 lastpoint = (x, y)
200 newcol = -1
201 #W.SetCursor('fist')
202 while Evt.Button():
Just van Rossumf376ef02001-11-18 14:12:43 +0000203 Evt.WaitNextEvent(0, 1, None) # needed for OSX
Just van Rossum40f9b7b1999-01-30 22:39:17 +0000204 (x, y) = Evt.GetMouse()
205 if (x, y) <> lastpoint:
206 newcol = x + diff
207 newcol = max(newcol, mincol)
208 newcol = min(newcol, maxcol)
209 Qd.PaintRect(rect)
210 rect = newcol - 1, t, newcol, b
211 Qd.PaintRect(rect)
212 lastpoint = (x, y)
213 Qd.PaintRect(rect)
214 Qd.PenPat(Qd.qd.black)
215 Qd.PenNormal()
216 if newcol > 0 and newcol <> abscol:
217 self.setcolumn(newcol - l)
218
219 def click(self, point, modifiers):
220 if point == (-1, -1): # gross.
221 W.List.click(self, point ,modifiers)
222 return
223 hit, index = self.inarrow(point)
224 if hit:
225 (key, value, arrow, indent) = self.items[index]
226 self.fold(index, arrow == 1)
227 elif self.incolumn(point):
228 self.trackcolumn(point)
229 else:
230 W.List.click(self, point, modifiers)
231
232 # for W.List.key
233 def findmatch(self, tag):
234 lower = string.lower
235 items = self.items
236 taglen = len(tag)
237 match = '\377' * 100
238 match_i = -1
239 for i in range(len(items)):
240 item = lower(str(items[i][0]))
241 if tag <= item < match:
242 match = item
243 match_i = i
244 if match_i >= 0:
245 return match_i
246 else:
247 return len(items) - 1
248
249 def close(self):
250 if self.closechildren:
251 for window in self.children:
252 window.close()
253 self.children = []
254 W.List.close(self)
255
256 def fold(self, index, onoff):
257 (key, value, arrow, indent) = self.items[index]
258 if arrow == 0 or (onoff and arrow == 2) or (not onoff and arrow == 1):
259 return
260 W.SetCursor('watch')
261 topcell = self.gettopcell()
262 if onoff:
263 self[index] = (key, value, 4, indent)
264 self.setdrawingmode(0)
265 self[index+1:index+1] = self.unpack(value, indent + 1)
266 self[index] = (key, value, 2, indent)
267 else:
268 self[index] = (key, value, 3, indent)
269 self.setdrawingmode(0)
270 count = 0
271 for i in range(index + 1, len(self.items)):
272 (dummy, dummy, dummy, subindent) = self.items[i]
273 if subindent <= indent:
274 break
275 count = count + 1
276 self[index+1:index+1+count] = []
277 self[index] = (key, value, 1, indent)
278 maxindent = self.getmaxindent()
279 if maxindent <> self.lastmaxindent:
280 newabsindent = self.col + (maxindent - self.lastmaxindent) * self.indent
281 if newabsindent >= self.mincol:
282 self.setcolumn(newabsindent)
283 self.lastmaxindent = maxindent
284 self.settopcell(topcell)
285 self.setdrawingmode(1)
286
287 def unfoldobjects(self, objects):
288 for obj in objects:
289 try:
290 index = self.items.index(obj)
291 except ValueError:
292 pass
293 else:
294 self.fold(index, 1)
295
296 def getunfoldedobjects(self):
297 curindent = 0
298 objects = []
299 for index in range(len(self.items)):
300 (key, value, arrow, indent) = self.items[index]
301 if indent > curindent:
302 (k, v, a, i) = self.items[index - 1]
303 objects.append((k, v, 1, i))
304 curindent = indent
305 elif indent < curindent:
306 curindent = indent
307 return objects
308
309 def listhit(self, isdbl):
310 if isdbl:
311 self.openselection()
312
313 def openselection(self):
314 import os
315 sel = self.getselection()
316 for index in sel:
317 (key, value, arrow, indent) = self[index]
318 if arrow:
319 self.children.append(Browser(value))
320 elif type(value) == types.StringType and '\0' not in value:
321 editor = self._parentwindow.parent.getscript(value)
322 if editor:
323 editor.select()
324 return
325 elif os.path.exists(value) and os.path.isfile(value):
326 import macfs
327 fss = macfs.FSSpec(value)
328 if fss.GetCreatorType()[1] == 'TEXT':
329 W.getapplication().openscript(value)
330
331 def itemrepr(self, (key, value, arrow, indent), str = str, double_repr = double_repr,
332 arrows = arrows, pack = struct.pack):
333 arrow = arrows[arrow]
334 return arrow + pack('h', self.indent * indent) + self.colstr + \
335 double_repr(key, value, 1)
336
337 def getmaxindent(self, max = max):
338 maxindent = 0
339 for item in self.items:
340 maxindent = max(maxindent, item[3])
341 return maxindent
342
343 def domenu_copy(self, *args):
344 sel = self.getselectedobjects()
345 selitems = []
346 for key, value, dummy, dummy in sel:
347 selitems.append(double_repr(key, value))
348 text = string.join(selitems, '\r')
349 if text:
Just van Rossum01c98052001-11-02 19:21:34 +0000350 from Carbon import Scrap
Jack Jansen65293682001-12-31 15:08:04 +0000351 if hasattr(Scrap, 'PutScrap'):
352 Scrap.ZeroScrap()
353 Scrap.PutScrap('TEXT', text)
354 else:
355 Scrap.ClearCurrentScrap()
356 sc = Scrap.GetCurrentScrap()
357 sc.PutScrapFlavor('TEXT', 0, text)
Just van Rossum40f9b7b1999-01-30 22:39:17 +0000358
Just van Rossum4e6d13c2002-03-26 12:06:11 +0000359 def listDefDraw(self, selected, cellRect, theCell,
360 dataOffset, dataLen, theList):
361 self.myDrawCell(0, selected, cellRect, theCell,
362 dataOffset, dataLen, theList)
363
364 def listDefHighlight(self, selected, cellRect, theCell,
365 dataOffset, dataLen, theList):
366 self.myDrawCell(1, selected, cellRect, theCell,
367 dataOffset, dataLen, theList)
368
369 def myDrawCell(self, onlyHilite, selected, cellRect, theCell,
370 dataOffset, dataLen, theList):
371 savedPort = Qd.GetPort()
372 Qd.SetPort(GetListPort(theList))
373 savedClip = Qd.NewRgn()
374 Qd.GetClip(savedClip)
375 Qd.ClipRect(cellRect)
376 savedPenState = Qd.GetPenState()
377 Qd.PenNormal()
378
379 l, t, r, b = cellRect
380
381 if not onlyHilite:
382 Qd.EraseRect(cellRect)
383
384 ascent, descent, leading, size, hm = Fm.FontMetrics()
385 linefeed = ascent + descent + leading
386
387 if dataLen >= 6:
388 data = theList.LGetCell(dataLen, theCell)
389 iconId, indent, tab = struct.unpack("hhh", data[:6])
390 key, value = data[6:].split("\t", 1)
391
392 if iconId:
393 theIcon = Icn.GetCIcon(iconId)
394 rect = (0, 0, 16, 16)
395 rect = Qd.OffsetRect(rect, l, t)
396 rect = Qd.OffsetRect(rect, 0, (theList.cellSize[1] - (rect[3] - rect[1])) / 2)
397 Icn.PlotCIcon(rect, theIcon)
398
399 if len(key) >= 0:
400 cl, ct, cr, cb = cellRect
401 vl, vt, vr, vb = self._viewbounds
402 cl = vl + PICTWIDTH + indent
403 cr = vl + tab
404 if cr > vr:
405 cr = vr
406 if cl < cr:
407 drawTextCell(key, (cl, ct, cr, cb), ascent, theList)
408 cl = vl + tab
409 cr = vr
410 if cl < cr:
411 drawTextCell(value, (cl, ct, cr, cb), ascent, theList)
412 #elif dataLen != 0:
413 # drawTextCell("???", 3, cellRect, ascent, theList)
414
415 # draw nice dotted line
416 l, t, r, b = cellRect
417 l = self._viewbounds[0] + tab
418 r = l + 1;
419 if not (theList.cellSize[1] & 0x01) or (t & 0x01):
420 myPat = "\xff\x00\xff\x00\xff\x00\xff\x00"
421 else:
422 myPat = "\x00\xff\x00\xff\x00\xff\x00\xff"
423 Qd.PenPat(myPat)
424 Qd.PenMode(QuickDraw.srcCopy)
425 Qd.PaintRect((l, t, r, b))
426 Qd.PenNormal()
427
428 if selected or onlyHilite:
429 l, t, r, b = cellRect
430 l = self._viewbounds[0] + PICTWIDTH
431 r = self._viewbounds[2]
432 Qd.PenMode(hilitetransfermode)
433 Qd.PaintRect((l, t, r, b))
434
435 # restore graphics environment
436 Qd.SetPort(savedPort)
437 Qd.SetClip(savedClip)
438 Qd.DisposeRgn(savedClip)
439 Qd.SetPenState(savedPenState)
440
441
Just van Rossum40f9b7b1999-01-30 22:39:17 +0000442
443class Browser:
444
445 def __init__(self, object = None, title = None, closechildren = 0):
446 if hasattr(object, '__name__'):
447 name = object.__name__
448 else:
449 name = ''
450 if title is None:
451 title = 'Object browser'
452 if name:
453 title = title + ': ' + name
454 self.w = w = W.Window((300, 400), title, minsize = (100, 100))
455 w.info = W.TextBox((18, 8, -70, 15))
Just van Rossumf376ef02001-11-18 14:12:43 +0000456 w.updatebutton = W.BevelButton((-64, 4, 50, 16), 'Update', self.update)
Just van Rossum40f9b7b1999-01-30 22:39:17 +0000457 w.browser = BrowserWidget((-1, 24, 1, -14), None)
458 w.bind('cmdu', w.updatebutton.push)
459 w.open()
460 self.set(object, name)
461
462 def close(self):
463 if self.w.wid:
464 self.w.close()
465
466 def set(self, object, name = ''):
467 W.SetCursor('watch')
468 tp = type(object).__name__
469 try:
470 length = len(object)
471 except:
472 length = -1
473 if not name and hasattr(object, '__name__'):
474 name = object.__name__
475 if name:
476 info = name + ': ' + tp
477 else:
478 info = tp
479 if length >= 0:
480 if length == 1:
481 info = info + ' (%d element)' % length
482 else:
483 info = info + ' (%d elements)' % length
484 self.w.info.set(info)
485 self.w.browser.set(object)
486
487 def update(self):
488 self.w.browser.update()
489
490
491SIMPLE_TYPES = (
492 types.NoneType,
493 types.IntType,
494 types.LongType,
495 types.FloatType,
496 types.ComplexType,
497 types.StringType
498)
499
500INDEXING_TYPES = (
501 types.TupleType,
502 types.ListType,
503 types.DictionaryType
504)
505
506def unpack_object(object, indent = 0):
507 tp = type(object)
508 if tp in SIMPLE_TYPES and tp is not types.NoneType:
Just van Rossumdc3c6172001-06-19 21:37:33 +0000509 raise TypeError, "can't browse simple type: %s" % tp.__name__
Just van Rossum40f9b7b1999-01-30 22:39:17 +0000510 elif tp == types.DictionaryType:
511 return unpack_dict(object, indent)
512 elif tp in (types.TupleType, types.ListType):
513 return unpack_sequence(object, indent)
514 elif tp == types.InstanceType:
515 return unpack_instance(object, indent)
516 elif tp == types.ClassType:
517 return unpack_class(object, indent)
518 elif tp == types.ModuleType:
519 return unpack_dict(object.__dict__, indent)
520 else:
521 return unpack_other(object, indent)
522
523def unpack_sequence(seq, indent = 0):
524 items = map(None, range(len(seq)), seq)
525 items = map(lambda (k, v), type = type, simp = SIMPLE_TYPES, indent = indent:
526 (k, v, not type(v) in simp, indent), items)
527 return items
528
529def unpack_dict(dict, indent = 0):
530 items = dict.items()
531 return pack_items(items, indent)
532
533def unpack_instance(inst, indent = 0):
534 if hasattr(inst, '__pybrowse_unpack__'):
535 return unpack_object(inst.__pybrowse_unpack__(), indent)
536 else:
537 items = [('__class__', inst.__class__)] + inst.__dict__.items()
538 return pack_items(items, indent)
539
540def unpack_class(clss, indent = 0):
541 items = [('__bases__', clss.__bases__), ('__name__', clss.__name__)] + clss.__dict__.items()
542 return pack_items(items, indent)
543
544def unpack_other(object, indent = 0):
545 attrs = []
546 if hasattr(object, '__members__'):
547 attrs = attrs + object.__members__
548 if hasattr(object, '__methods__'):
549 attrs = attrs + object.__methods__
Just van Rossumbdb9d482001-12-31 08:57:57 +0000550 if hasattr(object, '__dict__'):
551 attrs = attrs + object.__dict__.keys()
552 if hasattr(object, '__slots__'):
553 # XXX??
554 attrs = attrs + object.__slots__
555 if hasattr(object, "__class__") and "__class__" not in attrs:
556 attrs.append("__class__")
557 if hasattr(object, "__doc__") and "__doc__" not in attrs:
558 attrs.append("__doc__")
Just van Rossum40f9b7b1999-01-30 22:39:17 +0000559 items = []
560 for attr in attrs:
561 items.append((attr, getattr(object, attr)))
562 return pack_items(items, indent)
563
564def pack_items(items, indent = 0):
565 items = map(lambda (k, v), type = type, simp = SIMPLE_TYPES, indent = indent:
566 (k, v, not type(v) in simp, indent),
567 items)
568 return tuple_caselesssort(items)
569
570def caselesssort(alist):
571 """Return a sorted copy of a list. If there are only strings in the list,
572 it will not consider case"""
573
574 try:
575 # turn ['FOO', 'aaBc', 'ABcD'] into [('foo', 'FOO'), ('aabc', 'aaBc'), ('abcd', 'ABcD')], if possible
576 tupledlist = map(lambda item, lower = string.lower: (lower(item), item), alist)
577 except TypeError:
578 # at least one element in alist is not a string, proceed the normal way...
579 alist = alist[:]
580 alist.sort()
581 return alist
582 else:
583 tupledlist.sort()
584 # turn [('aabc', 'aaBc'), ('abcd', 'ABcD'), ('foo', 'FOO')] into ['aaBc', 'ABcD', 'FOO']
585 return map(lambda x: x[1], tupledlist)
586
587def tuple_caselesssort(items):
588 try:
589 tupledlist = map(lambda tuple, lower = string.lower: (lower(tuple[0]), tuple), items)
Just van Rossum6508c7c2000-10-20 06:34:57 +0000590 except (AttributeError, TypeError):
Just van Rossum40f9b7b1999-01-30 22:39:17 +0000591 items = items[:]
592 items.sort()
593 return items
594 else:
595 tupledlist.sort()
596 return map(lambda (low, tuple): tuple, tupledlist)
597