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