Ditched '_find_SET()', since it was a no-value-added wrapper around
[python/dscho.git] / Mac / Tools / IDE / PyBrowser.py
blob27e6418886725f6ca98e6ee2c892cb7df1222953
1 import W
2 import Wkeys
3 import struct
4 import string
5 import types
6 import regex
8 nullid = '\0\0'
9 closedid = struct.pack('h', 468)
10 openid = struct.pack('h', 469)
11 closedsolidid = struct.pack('h', 470)
12 opensolidid = struct.pack('h', 471)
14 arrows = (nullid, closedid, openid, closedsolidid, opensolidid)
16 has_ctlcharsRE = regex.compile('[\000-\037\177-\377]')
18 def double_repr(key, value, truncvalue = 0,
19 type = type, StringType = types.StringType,
20 has_ctlchars = has_ctlcharsRE.search, _repr = repr, str = str):
21 if type(key) == StringType and has_ctlchars(key) < 0:
22 key = str(key)
23 else:
24 key = _repr(key)
25 if key == '__builtins__':
26 value = "<" + type(value).__name__ + " '__builtin__'>"
27 elif key == '__return__':
28 # bleh, when returning from a class codeblock we get infinite recursion in repr.
29 # Use safe repr instead.
30 import repr
31 value = repr.repr(value)
32 else:
33 try:
34 value = _repr(value)
35 '' + value # test to see if it is a string, in case a __repr__ method is buggy
36 except:
37 value = '€€€ exception in repr()'
38 if truncvalue:
39 return key + '\t' + value[:255]
40 return key + '\t' + value
43 class BrowserWidget(W.List):
45 LDEF_ID = 471
47 def __init__(self, possize, object = None, col = 100, closechildren = 0):
48 W.List.__init__(self, possize, callback = self.listhit)
49 self.object = (None,)
50 self.indent = 16
51 self.lastmaxindent = 0
52 self.closechildren = closechildren
53 self.children = []
54 self.mincol = 64
55 self.setcolumn(col)
56 self.bind('return', self.openselection)
57 self.bind('enter', self.openselection)
58 if object is not None:
59 self.set(object)
61 def set(self, object):
62 if self.object[0] is not object:
63 self.object = object,
64 self[:] = self.unpack(object, 0)
65 elif self._parentwindow is not None and self._parentwindow.wid:
66 self.update()
68 def unpack(self, object, indent):
69 return unpack_object(object, indent)
71 def update(self):
72 # for now...
73 W.SetCursor('watch')
74 self.setdrawingmode(0)
75 sel = self.getselectedobjects()
76 fold = self.getunfoldedobjects()
77 topcell = self.gettopcell()
78 self[:] = self.unpack(self.object[0], 0)
79 self.unfoldobjects(fold)
80 self.setselectedobjects(sel)
81 self.settopcell(topcell)
82 self.setdrawingmode(1)
84 def setcolumn(self, col):
85 self.col = col
86 self.colstr = struct.pack('h', col)
87 if self._list:
88 sel = self.getselection()
89 self.setitems(self.items)
90 self.setselection(sel)
92 def key(self, char, event):
93 if char in (Wkeys.leftarrowkey, Wkeys.rightarrowkey):
94 sel = self.getselection()
95 sel.reverse()
96 self.setdrawingmode(0)
97 for index in sel:
98 self.fold(index, char == Wkeys.rightarrowkey)
99 self.setdrawingmode(1)
100 else:
101 W.List.key(self, char, event)
103 def rollover(self, (x, y), onoff):
104 if onoff:
105 if self.incolumn((x, y)):
106 W.SetCursor('hmover')
107 else:
108 W.SetCursor('arrow')
110 def inarrow(self, (x, y)):
111 cl, ct, cr, cb = self._list.LRect((0, 0))
112 l, t, r, b = self._bounds
113 if (x - cl) < 16:
114 cellheight = cb - ct
115 index = (y - ct) / cellheight
116 if index < len(self.items):
117 return 1, index
118 return None, None
120 def incolumn(self, (x, y)):
121 l, t, r, b = self._list.LRect((0, 0))
122 abscol = l + self.col
123 return abs(abscol - x) < 3
125 def trackcolumn(self, (x, y)):
126 import Qd, QuickDraw, Evt
127 self.SetPort()
128 l, t, r, b = self._bounds
129 bounds = l, t, r, b = l + 1, t + 1, r - 16, b - 1
130 abscol = l + self.col
131 mincol = l + self.mincol
132 maxcol = r - 10
133 diff = abscol - x
134 Qd.PenPat('\000\377\000\377\000\377\000\377')
135 Qd.PenMode(QuickDraw.srcXor)
136 rect = abscol - 1, t, abscol, b
137 Qd.PaintRect(rect)
138 lastpoint = (x, y)
139 newcol = -1
140 #W.SetCursor('fist')
141 while Evt.Button():
142 (x, y) = Evt.GetMouse()
143 if (x, y) <> lastpoint:
144 newcol = x + diff
145 newcol = max(newcol, mincol)
146 newcol = min(newcol, maxcol)
147 Qd.PaintRect(rect)
148 rect = newcol - 1, t, newcol, b
149 Qd.PaintRect(rect)
150 lastpoint = (x, y)
151 Qd.PaintRect(rect)
152 Qd.PenPat(Qd.qd.black)
153 Qd.PenNormal()
154 if newcol > 0 and newcol <> abscol:
155 self.setcolumn(newcol - l)
157 def click(self, point, modifiers):
158 if point == (-1, -1): # gross.
159 W.List.click(self, point ,modifiers)
160 return
161 hit, index = self.inarrow(point)
162 if hit:
163 (key, value, arrow, indent) = self.items[index]
164 self.fold(index, arrow == 1)
165 elif self.incolumn(point):
166 self.trackcolumn(point)
167 else:
168 W.List.click(self, point, modifiers)
170 # for W.List.key
171 def findmatch(self, tag):
172 lower = string.lower
173 items = self.items
174 taglen = len(tag)
175 match = '\377' * 100
176 match_i = -1
177 for i in range(len(items)):
178 item = lower(str(items[i][0]))
179 if tag <= item < match:
180 match = item
181 match_i = i
182 if match_i >= 0:
183 return match_i
184 else:
185 return len(items) - 1
187 def close(self):
188 if self.closechildren:
189 for window in self.children:
190 window.close()
191 self.children = []
192 W.List.close(self)
194 def fold(self, index, onoff):
195 (key, value, arrow, indent) = self.items[index]
196 if arrow == 0 or (onoff and arrow == 2) or (not onoff and arrow == 1):
197 return
198 W.SetCursor('watch')
199 topcell = self.gettopcell()
200 if onoff:
201 self[index] = (key, value, 4, indent)
202 self.setdrawingmode(0)
203 self[index+1:index+1] = self.unpack(value, indent + 1)
204 self[index] = (key, value, 2, indent)
205 else:
206 self[index] = (key, value, 3, indent)
207 self.setdrawingmode(0)
208 count = 0
209 for i in range(index + 1, len(self.items)):
210 (dummy, dummy, dummy, subindent) = self.items[i]
211 if subindent <= indent:
212 break
213 count = count + 1
214 self[index+1:index+1+count] = []
215 self[index] = (key, value, 1, indent)
216 maxindent = self.getmaxindent()
217 if maxindent <> self.lastmaxindent:
218 newabsindent = self.col + (maxindent - self.lastmaxindent) * self.indent
219 if newabsindent >= self.mincol:
220 self.setcolumn(newabsindent)
221 self.lastmaxindent = maxindent
222 self.settopcell(topcell)
223 self.setdrawingmode(1)
225 def unfoldobjects(self, objects):
226 for obj in objects:
227 try:
228 index = self.items.index(obj)
229 except ValueError:
230 pass
231 else:
232 self.fold(index, 1)
234 def getunfoldedobjects(self):
235 curindent = 0
236 objects = []
237 for index in range(len(self.items)):
238 (key, value, arrow, indent) = self.items[index]
239 if indent > curindent:
240 (k, v, a, i) = self.items[index - 1]
241 objects.append((k, v, 1, i))
242 curindent = indent
243 elif indent < curindent:
244 curindent = indent
245 return objects
247 def listhit(self, isdbl):
248 if isdbl:
249 self.openselection()
251 def openselection(self):
252 import os
253 sel = self.getselection()
254 for index in sel:
255 (key, value, arrow, indent) = self[index]
256 if arrow:
257 self.children.append(Browser(value))
258 elif type(value) == types.StringType and '\0' not in value:
259 editor = self._parentwindow.parent.getscript(value)
260 if editor:
261 editor.select()
262 return
263 elif os.path.exists(value) and os.path.isfile(value):
264 import macfs
265 fss = macfs.FSSpec(value)
266 if fss.GetCreatorType()[1] == 'TEXT':
267 W.getapplication().openscript(value)
269 def itemrepr(self, (key, value, arrow, indent), str = str, double_repr = double_repr,
270 arrows = arrows, pack = struct.pack):
271 arrow = arrows[arrow]
272 return arrow + pack('h', self.indent * indent) + self.colstr + \
273 double_repr(key, value, 1)
275 def getmaxindent(self, max = max):
276 maxindent = 0
277 for item in self.items:
278 maxindent = max(maxindent, item[3])
279 return maxindent
281 def domenu_copy(self, *args):
282 sel = self.getselectedobjects()
283 selitems = []
284 for key, value, dummy, dummy in sel:
285 selitems.append(double_repr(key, value))
286 text = string.join(selitems, '\r')
287 if text:
288 import Scrap
289 Scrap.ZeroScrap()
290 Scrap.PutScrap('TEXT', text)
293 class Browser:
295 def __init__(self, object = None, title = None, closechildren = 0):
296 if hasattr(object, '__name__'):
297 name = object.__name__
298 else:
299 name = ''
300 if title is None:
301 title = 'Object browser'
302 if name:
303 title = title + ': ' + name
304 self.w = w = W.Window((300, 400), title, minsize = (100, 100))
305 w.info = W.TextBox((18, 8, -70, 15))
306 w.updatebutton = W.Button((-64, 4, 50, 16), 'Update', self.update)
307 w.browser = BrowserWidget((-1, 24, 1, -14), None)
308 w.bind('cmdu', w.updatebutton.push)
309 w.open()
310 self.set(object, name)
312 def close(self):
313 if self.w.wid:
314 self.w.close()
316 def set(self, object, name = ''):
317 W.SetCursor('watch')
318 tp = type(object).__name__
319 try:
320 length = len(object)
321 except:
322 length = -1
323 if not name and hasattr(object, '__name__'):
324 name = object.__name__
325 if name:
326 info = name + ': ' + tp
327 else:
328 info = tp
329 if length >= 0:
330 if length == 1:
331 info = info + ' (%d element)' % length
332 else:
333 info = info + ' (%d elements)' % length
334 self.w.info.set(info)
335 self.w.browser.set(object)
337 def update(self):
338 self.w.browser.update()
341 SIMPLE_TYPES = (
342 types.NoneType,
343 types.IntType,
344 types.LongType,
345 types.FloatType,
346 types.ComplexType,
347 types.StringType
350 INDEXING_TYPES = (
351 types.TupleType,
352 types.ListType,
353 types.DictionaryType
356 def unpack_object(object, indent = 0):
357 tp = type(object)
358 if tp in SIMPLE_TYPES and tp is not types.NoneType:
359 raise TypeError, 'can¹t browse simple type: %s' % tp.__name__
360 elif tp == types.DictionaryType:
361 return unpack_dict(object, indent)
362 elif tp in (types.TupleType, types.ListType):
363 return unpack_sequence(object, indent)
364 elif tp == types.InstanceType:
365 return unpack_instance(object, indent)
366 elif tp == types.ClassType:
367 return unpack_class(object, indent)
368 elif tp == types.ModuleType:
369 return unpack_dict(object.__dict__, indent)
370 else:
371 return unpack_other(object, indent)
373 def unpack_sequence(seq, indent = 0):
374 items = map(None, range(len(seq)), seq)
375 items = map(lambda (k, v), type = type, simp = SIMPLE_TYPES, indent = indent:
376 (k, v, not type(v) in simp, indent), items)
377 return items
379 def unpack_dict(dict, indent = 0):
380 items = dict.items()
381 return pack_items(items, indent)
383 def unpack_instance(inst, indent = 0):
384 if hasattr(inst, '__pybrowse_unpack__'):
385 return unpack_object(inst.__pybrowse_unpack__(), indent)
386 else:
387 items = [('__class__', inst.__class__)] + inst.__dict__.items()
388 return pack_items(items, indent)
390 def unpack_class(clss, indent = 0):
391 items = [('__bases__', clss.__bases__), ('__name__', clss.__name__)] + clss.__dict__.items()
392 return pack_items(items, indent)
394 def unpack_other(object, indent = 0):
395 attrs = []
396 if hasattr(object, '__members__'):
397 attrs = attrs + object.__members__
398 if hasattr(object, '__methods__'):
399 attrs = attrs + object.__methods__
400 items = []
401 for attr in attrs:
402 items.append((attr, getattr(object, attr)))
403 return pack_items(items, indent)
405 def pack_items(items, indent = 0):
406 items = map(lambda (k, v), type = type, simp = SIMPLE_TYPES, indent = indent:
407 (k, v, not type(v) in simp, indent),
408 items)
409 return tuple_caselesssort(items)
411 def caselesssort(alist):
412 """Return a sorted copy of a list. If there are only strings in the list,
413 it will not consider case"""
415 try:
416 # turn ['FOO', 'aaBc', 'ABcD'] into [('foo', 'FOO'), ('aabc', 'aaBc'), ('abcd', 'ABcD')], if possible
417 tupledlist = map(lambda item, lower = string.lower: (lower(item), item), alist)
418 except TypeError:
419 # at least one element in alist is not a string, proceed the normal way...
420 alist = alist[:]
421 alist.sort()
422 return alist
423 else:
424 tupledlist.sort()
425 # turn [('aabc', 'aaBc'), ('abcd', 'ABcD'), ('foo', 'FOO')] into ['aaBc', 'ABcD', 'FOO']
426 return map(lambda x: x[1], tupledlist)
428 def tuple_caselesssort(items):
429 try:
430 tupledlist = map(lambda tuple, lower = string.lower: (lower(tuple[0]), tuple), items)
431 except TypeError:
432 items = items[:]
433 items.sort()
434 return items
435 else:
436 tupledlist.sort()
437 return map(lambda (low, tuple): tuple, tupledlist)