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