| OLD | NEW |
| (Empty) |
| 1 # -*- Python -*- | |
| 2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 3 # See LICENSE for details. | |
| 4 | |
| 5 | |
| 6 """Object browser GUI, GnomeCanvas implementation. | |
| 7 """ | |
| 8 | |
| 9 from twisted.python import log | |
| 10 | |
| 11 # TODO: | |
| 12 # gzigzag-style navigation | |
| 13 | |
| 14 class SillyModule: | |
| 15 def __init__(self, module, prefix): | |
| 16 self.__module = module | |
| 17 self.__prefix = prefix | |
| 18 | |
| 19 def __getattr__(self, attr): | |
| 20 try: | |
| 21 return getattr(self.__module, self.__prefix + attr) | |
| 22 except AttributeError: | |
| 23 return getattr(self.__module, attr) | |
| 24 | |
| 25 | |
| 26 # We use gnome.ui because that's what happens to have Python bindings | |
| 27 # for the Canvas. I think this canvas widget is available seperately | |
| 28 # in "libart", but nobody's given me Python bindings for just that. | |
| 29 | |
| 30 # The Gnome canvas is said to be modeled after the Tk canvas, so we | |
| 31 # could probably write this in Tk too. But my experience is with GTK, | |
| 32 # not with Tk, so this is what I use. | |
| 33 | |
| 34 import gnome.ui | |
| 35 gnome = SillyModule(gnome.ui, 'Gnome') | |
| 36 | |
| 37 import gtk | |
| 38 (True, False) = (gtk.TRUE, gtk.FALSE) | |
| 39 gtk = SillyModule(gtk, 'Gtk') | |
| 40 | |
| 41 import GDK | |
| 42 | |
| 43 from twisted.python import reflect, text | |
| 44 from twisted.spread import pb | |
| 45 from twisted.manhole import explorer | |
| 46 | |
| 47 import string, sys, types | |
| 48 import UserList | |
| 49 _PIXELS_PER_UNIT=10 | |
| 50 | |
| 51 #### Support class. | |
| 52 | |
| 53 class PairList(UserList.UserList): | |
| 54 """An ordered list of key, value pairs. | |
| 55 | |
| 56 Kinda like an ordered dictionary. Made with small data sets | |
| 57 in mind, as get() does a linear search, not hashing. | |
| 58 """ | |
| 59 def get(self, key): | |
| 60 i = 0 | |
| 61 for k, v in self.data: | |
| 62 if key == k: | |
| 63 return (i, v) | |
| 64 i = i + 1 | |
| 65 else: | |
| 66 return (None, None) | |
| 67 | |
| 68 def keys(self): | |
| 69 return map(lambda x: x[0], self.data) | |
| 70 | |
| 71 | |
| 72 #### Public | |
| 73 | |
| 74 class SpelunkDisplay(gnome.Canvas): | |
| 75 """Spelunk widget. | |
| 76 | |
| 77 The top-level widget for this module. This gtk.Widget is where the | |
| 78 explorer display will be, and this object is also your interface to | |
| 79 creating new visages. | |
| 80 """ | |
| 81 def __init__(self, aa=False): | |
| 82 gnome.Canvas.__init__(self, aa) | |
| 83 self.set_pixels_per_unit(_PIXELS_PER_UNIT) | |
| 84 self.visages = {} | |
| 85 | |
| 86 def makeDefaultCanvas(self): | |
| 87 """Make myself the default canvas which new visages are created on. | |
| 88 """ | |
| 89 # XXX: For some reason, the 'canvas' and 'parent' properties | |
| 90 # of CanvasItems aren't accessible thorugh pygnome. | |
| 91 Explorer.canvas = self | |
| 92 | |
| 93 def receiveExplorer(self, xplorer): | |
| 94 if self.visages.has_key(xplorer.id): | |
| 95 log.msg("Using cached visage for %d" % (xplorer.id, )) | |
| 96 # Ikk. Just because we just received this explorer, that | |
| 97 # doesn't necessarily mean its attributes are fresh. Fix | |
| 98 # that, either by having this side pull or the server | |
| 99 # side push. | |
| 100 visage = self.visages[xplorer.id] | |
| 101 #xplorer.give_properties(visage) | |
| 102 #xplorer.give_attributes(visage) | |
| 103 else: | |
| 104 log.msg("Making new visage for %d" % (xplorer.id, )) | |
| 105 self.visages[xplorer.id] = xplorer.newVisage(self.root(), | |
| 106 self) | |
| 107 | |
| 108 #### Base classes | |
| 109 | |
| 110 class Explorer(pb.RemoteCache): | |
| 111 """Base class for all RemoteCaches of explorer.Explorer cachables. | |
| 112 | |
| 113 Meaning that when an Explorer comes back over the wire, one of | |
| 114 these is created. From this, you can make a Visage for the | |
| 115 SpelunkDisplay, or a widget to display as an Attribute. | |
| 116 """ | |
| 117 canvas = None | |
| 118 # From our cache: | |
| 119 id = None | |
| 120 identifier = None | |
| 121 explorerClass = None | |
| 122 attributeGroups = None | |
| 123 | |
| 124 def newVisage(self, group, canvas=None): | |
| 125 """Make a new visage for the object I explore. | |
| 126 | |
| 127 Returns a Visage. | |
| 128 """ | |
| 129 canvas = canvas or self.canvas | |
| 130 klass = spelunkerClassTable.get(self.explorerClass, None) | |
| 131 if (not klass) or (klass[0] is None): | |
| 132 log.msg("%s not in table, using generic" % self.explorerClass) | |
| 133 klass = GenericVisage | |
| 134 else: | |
| 135 klass = klass[0] | |
| 136 spelunker = klass(self, group, canvas) | |
| 137 if hasattr(canvas, "visages") \ | |
| 138 and not canvas.visages.has_key(self.id): | |
| 139 canvas.visages[self.id] = spelunker | |
| 140 | |
| 141 self.give_properties(spelunker) | |
| 142 | |
| 143 self.give_attributes(spelunker) | |
| 144 | |
| 145 return spelunker | |
| 146 | |
| 147 def newAttributeWidget(self, group): | |
| 148 """Make a new attribute item for my object. | |
| 149 | |
| 150 Returns a gtk.Widget. | |
| 151 """ | |
| 152 klass = spelunkerClassTable.get(self.explorerClass, None) | |
| 153 if (not klass) or (klass[1] is None): | |
| 154 log.msg("%s not in table, using generic" % self.explorerClass) | |
| 155 klass = GenericAttributeWidget | |
| 156 else: | |
| 157 klass = klass[1] | |
| 158 | |
| 159 return klass(self, group) | |
| 160 | |
| 161 def give_properties(self, spelunker): | |
| 162 """Give a spelunker my properties in an ordered list. | |
| 163 """ | |
| 164 valuelist = PairList() | |
| 165 for p in spelunker.propertyLabels.keys(): | |
| 166 value = getattr(self, p, None) | |
| 167 valuelist.append((p,value)) | |
| 168 spelunker.fill_properties(valuelist) | |
| 169 | |
| 170 def give_attributes(self, spelunker): | |
| 171 for a in spelunker.groupLabels.keys(): | |
| 172 things = getattr(self, a) | |
| 173 spelunker.fill_attributeGroup(a, things) | |
| 174 | |
| 175 class _LooseBoxBorder: | |
| 176 box = None | |
| 177 color = 'black' | |
| 178 width = 1 | |
| 179 def __init__(self, box): | |
| 180 self.box = box | |
| 181 | |
| 182 class LooseBox(gnome.CanvasGroup): | |
| 183 def __init__(self): | |
| 184 self.border = _LooseBoxBorder(self) | |
| 185 | |
| 186 class Visage(gnome.CanvasGroup): | |
| 187 """A \"face\" of an object under exploration. | |
| 188 | |
| 189 A Visage is a representation of an object presented to the user. | |
| 190 The \"face\" in \"interface\". | |
| 191 | |
| 192 'propertyLabels' and 'groupLabels' are lists of (key, name) | |
| 193 2-ples, with 'key' being the string the property or group is | |
| 194 denoted by in the code, and 'name' being the pretty human-readable | |
| 195 string you want me to show on the Visage. These attributes are | |
| 196 accumulated from base classes as well. | |
| 197 | |
| 198 I am a gnome.CanvasItem (more specifically, CanvasGroup). | |
| 199 """ | |
| 200 color = {'border': '#006644'} | |
| 201 border_width = 8 | |
| 202 detail_level = 0 | |
| 203 # These are mappings from the strings the code calls these by | |
| 204 # and the pretty names you want to see on the screen. | |
| 205 # (e.g. Capitalized or localized) | |
| 206 propertyLabels = [] | |
| 207 groupLabels = [] | |
| 208 | |
| 209 drag_x0 = 0 | |
| 210 drag_y0 = 0 | |
| 211 | |
| 212 def __init__(self, explorer, rootGroup, canvas): | |
| 213 """Place a new Visage of an explorer in a canvas group. | |
| 214 | |
| 215 I also need a 'canvas' reference is for certain coordinate | |
| 216 conversions, and pygnome doesn't give access to my GtkObject's | |
| 217 .canvas attribute. :( | |
| 218 """ | |
| 219 # Ugh. PyGtk/GtkObject/GnomeCanvas interfacing grits. | |
| 220 gnome.CanvasGroup.__init__(self, | |
| 221 _obj = rootGroup.add('group')._o) | |
| 222 | |
| 223 self.propertyLabels = PairList() | |
| 224 reflect.accumulateClassList(self.__class__, 'propertyLabels', | |
| 225 self.propertyLabels) | |
| 226 self.groupLabels = PairList() | |
| 227 reflect.accumulateClassList(self.__class__, 'groupLabels', | |
| 228 self.groupLabels) | |
| 229 | |
| 230 self.explorer = explorer | |
| 231 self.identifier = explorer.identifier | |
| 232 self.objectId = explorer.id | |
| 233 | |
| 234 self.canvas = canvas | |
| 235 self.rootGroup = rootGroup | |
| 236 | |
| 237 self.ebox = gtk.EventBox() | |
| 238 self.ebox.set_name("Visage") | |
| 239 self.frame = gtk.Frame(self.identifier) | |
| 240 self.container = gtk.VBox() | |
| 241 self.ebox.add(self.frame) | |
| 242 self.frame.add(self.container) | |
| 243 | |
| 244 self.canvasWidget = self.add('widget', widget=self.ebox, | |
| 245 x=0, y=0, anchor=gtk.ANCHOR_NW, | |
| 246 size_pixels=0) | |
| 247 | |
| 248 self.border = self.add('rect', x1=0, y1=0, | |
| 249 x2=1, y2=1, | |
| 250 fill_color=None, | |
| 251 outline_color=self.color['border'], | |
| 252 width_pixels=self.border_width) | |
| 253 | |
| 254 self.subtable = {} | |
| 255 | |
| 256 self._setup_table() | |
| 257 | |
| 258 # TODO: | |
| 259 # Collapse me | |
| 260 # Movable/resizeable me | |
| 261 # Destroy me | |
| 262 # Set my detail level | |
| 263 | |
| 264 self.frame.connect("size_allocate", self.signal_size_allocate, | |
| 265 None) | |
| 266 self.connect("destroy", self.signal_destroy, None) | |
| 267 self.connect("event", self.signal_event) | |
| 268 | |
| 269 self.ebox.show_all() | |
| 270 | |
| 271 # Our creator will call our fill_ methods when she has the goods. | |
| 272 | |
| 273 def _setup_table(self): | |
| 274 """Called by __init__ to set up my main table. | |
| 275 | |
| 276 You can easily override me instead of clobbering __init__. | |
| 277 """ | |
| 278 | |
| 279 table = gtk.Table(len(self.propertyLabels), 2) | |
| 280 self.container.add(table) | |
| 281 table.set_name("PropertyTable") | |
| 282 self.subtable['properties'] = table | |
| 283 row = 0 | |
| 284 | |
| 285 for p, name in self.propertyLabels: | |
| 286 label = gtk.Label(name) | |
| 287 label.set_name("PropertyName") | |
| 288 label.set_data("property", p) | |
| 289 table.attach(label, 0, 1, row, row + 1) | |
| 290 label.set_alignment(0, 0) | |
| 291 row = row + 1 | |
| 292 | |
| 293 # XXX: make these guys collapsable | |
| 294 for g, name in self.groupLabels: | |
| 295 table = gtk.Table(1, 2) | |
| 296 self.container.add(table) | |
| 297 table.set_name("AttributeGroupTable") | |
| 298 self.subtable[g] = table | |
| 299 label = gtk.Label(name) | |
| 300 label.set_name("AttributeGroupTitle") | |
| 301 table.attach(label, 0, 2, 0, 1) | |
| 302 | |
| 303 def fill_properties(self, propValues): | |
| 304 """Fill in values for my properites. | |
| 305 | |
| 306 Takes a list of (name, value) pairs. 'name' should be one of | |
| 307 the keys in my propertyLabels, and 'value' either an Explorer | |
| 308 or a string. | |
| 309 """ | |
| 310 table = self.subtable['properties'] | |
| 311 | |
| 312 table.resize(len(propValues), 2) | |
| 313 | |
| 314 # XXX: Do I need to destroy previously attached children? | |
| 315 | |
| 316 for name, value in propValues: | |
| 317 self.fill_property(name, value) | |
| 318 | |
| 319 table.show_all() | |
| 320 | |
| 321 def fill_property(self, property, value): | |
| 322 """Set a value for a particular property. | |
| 323 | |
| 324 'property' should be one of the keys in my propertyLabels. | |
| 325 """ | |
| 326 row, name = self.propertyLabels.get(property) | |
| 327 if type(value) is not types.InstanceType: | |
| 328 widget = gtk.Label(str(value)) | |
| 329 widget.set_alignment(0, 0) | |
| 330 else: | |
| 331 widget = value.newAttributeWidget(self) | |
| 332 widget.set_name("PropertyValue") | |
| 333 | |
| 334 self.subtable['properties'].attach(widget, 1, 2, row, row+1) | |
| 335 | |
| 336 def fill_attributeGroup(self, group, attributes): | |
| 337 """Provide members of an attribute group. | |
| 338 | |
| 339 'group' should be one of the keys in my groupLabels, and | |
| 340 'attributes' a list of (name, value) pairs, with each value as | |
| 341 either an Explorer or string. | |
| 342 """ | |
| 343 | |
| 344 # XXX: How to indicate detail level of members? | |
| 345 | |
| 346 table = self.subtable[group] | |
| 347 if not attributes: | |
| 348 table.hide() | |
| 349 return | |
| 350 | |
| 351 table.resize(len(attributes)+1, 2) | |
| 352 | |
| 353 # XXX: Do I need to destroy previously attached children? | |
| 354 | |
| 355 row = 1 # 0 is title | |
| 356 | |
| 357 for name, value in attributes.items(): | |
| 358 label = gtk.Label(name) | |
| 359 label.set_name("AttributeName") | |
| 360 label.set_alignment(0, 0) | |
| 361 | |
| 362 if type(value) is types.StringType: | |
| 363 widget = gtk.Label(value) | |
| 364 widget.set_alignment(0, 0) | |
| 365 else: | |
| 366 widget = value.newAttributeWidget(self) | |
| 367 | |
| 368 table.attach(label, 0, 1, row, row + 1) | |
| 369 table.attach(widget, 1, 2, row, row + 1) | |
| 370 row = row + 1 | |
| 371 | |
| 372 table.show_all() | |
| 373 | |
| 374 def signal_event(self, widget, event=None): | |
| 375 if not event: | |
| 376 log.msg("Huh? got event signal with no event.") | |
| 377 return | |
| 378 if event.type == GDK.BUTTON_PRESS: | |
| 379 if event.button == 1: | |
| 380 self.drag_x0, self.drag_y0 = event.x, event.y | |
| 381 return True | |
| 382 elif event.type == GDK.MOTION_NOTIFY: | |
| 383 if event.state & GDK.BUTTON1_MASK: | |
| 384 self.move(event.x - self.drag_x0, event.y - self.drag_y0) | |
| 385 self.drag_x0, self.drag_y0 = event.x, event.y | |
| 386 return True | |
| 387 return False | |
| 388 | |
| 389 def signal_size_allocate(self, frame_widget, | |
| 390 unusable_allocation, unused_data): | |
| 391 (x, y, w, h) = frame_widget.get_allocation() | |
| 392 | |
| 393 # XXX: allocation PyCObject is apparently unusable! | |
| 394 # (w, h) = allocation.width, allocation.height | |
| 395 | |
| 396 w, h = (float(w)/_PIXELS_PER_UNIT, float(h)/_PIXELS_PER_UNIT) | |
| 397 | |
| 398 x1, y1 = (self.canvasWidget['x'], self.canvasWidget['y']) | |
| 399 | |
| 400 b = self.border | |
| 401 (b['x1'], b['y1'], b['x2'], b['y2']) = (x1, y1, x1+w, y1+h) | |
| 402 | |
| 403 def signal_destroy(self, unused_object, unused_data): | |
| 404 del self.explorer | |
| 405 | |
| 406 del self.canvasWidget | |
| 407 del self.border | |
| 408 | |
| 409 del self.ebox | |
| 410 del self.frame | |
| 411 del self.container | |
| 412 | |
| 413 self.subtable.clear() | |
| 414 | |
| 415 | |
| 416 class AttributeWidget(gtk.Widget): | |
| 417 """A widget briefly describing an object. | |
| 418 | |
| 419 This is similar to a Visage, but has far less detail. This should | |
| 420 display only essential identifiying information, a gtk.Widget | |
| 421 suitable for including in a single table cell. | |
| 422 | |
| 423 (gtk.Widgets are used here instead of the more graphically | |
| 424 pleasing gnome.CanvasItems because I was too lazy to re-write | |
| 425 gtk.table for the canvas. A new table widget/item would be great | |
| 426 though, not only for canvas prettiness, but also because we could | |
| 427 use one with a mone pythonic API.) | |
| 428 | |
| 429 """ | |
| 430 def __init__(self, explorer, parent): | |
| 431 """A new AttributeWidget describing an explorer. | |
| 432 """ | |
| 433 self.parent = parent | |
| 434 | |
| 435 self.explorer = explorer | |
| 436 self.identifier = explorer.identifier | |
| 437 self.id = explorer.id | |
| 438 | |
| 439 widgetObj = self._makeWidgetObject() | |
| 440 gtk.Widget.__init__(self, _obj=widgetObj) | |
| 441 self.set_name("AttributeValue") | |
| 442 self.connect("destroy", self.signal_destroy, None) | |
| 443 self.connect("button-press-event", self.signal_buttonPressEvent, | |
| 444 None) | |
| 445 | |
| 446 def getTextForLabel(self): | |
| 447 """Returns text for my label. | |
| 448 | |
| 449 The default implementation of AttributeWidget is a gtk.Label | |
| 450 widget. You may override this method to change the text which | |
| 451 appears in the label. However, if you don't want to be a | |
| 452 label, override _makeWidgetObject instead. | |
| 453 """ | |
| 454 return self.identifier | |
| 455 | |
| 456 def _makeWidgetObject(self): | |
| 457 """Make the GTK widget object that is me. | |
| 458 | |
| 459 Called by __init__ to construct the GtkObject I wrap-- the ._o | |
| 460 member of a pygtk GtkObject. Isn't subclassing GtkObjects in | |
| 461 Python fun? | |
| 462 """ | |
| 463 ebox = gtk.EventBox() | |
| 464 label = gtk.Label(self.getTextForLabel()) | |
| 465 label.set_alignment(0,0) | |
| 466 ebox.add(label) | |
| 467 return ebox._o | |
| 468 | |
| 469 def signal_destroy(self, unused_object, unused_data): | |
| 470 del self.explorer | |
| 471 | |
| 472 def signal_buttonPressEvent(self, widget, eventButton, unused_data): | |
| 473 if eventButton.type == GDK._2BUTTON_PRESS: | |
| 474 if self.parent.canvas.visages.has_key(self.explorer.id): | |
| 475 visage = self.parent.canvas.visages[self.explorer.id] | |
| 476 else: | |
| 477 visage = self.explorer.newVisage(self.parent.rootGroup, | |
| 478 self.parent.canvas) | |
| 479 (x, y, w, h) = self.get_allocation() | |
| 480 wx, wy = self.parent.canvas.c2w(x, y) | |
| 481 | |
| 482 x1, y1, x2, y2 = self.parent.get_bounds() | |
| 483 | |
| 484 v_x1, v_y1, v_x2, v_y2 = visage.get_bounds() | |
| 485 | |
| 486 visage.move(x2 - v_x1, wy + y1 - v_y1) | |
| 487 | |
| 488 | |
| 489 #### Widget-specific subclasses of Explorer, Visage, and Attribute | |
| 490 | |
| 491 # Instance | |
| 492 | |
| 493 class ExplorerInstance(Explorer): | |
| 494 pass | |
| 495 | |
| 496 class InstanceVisage(Visage): | |
| 497 # Detail levels: | |
| 498 # Just me | |
| 499 # me and my class | |
| 500 # me and my whole class heirarchy | |
| 501 | |
| 502 propertyLabels = [('klass', "Class")] | |
| 503 groupLabels = [('data', "Data"), | |
| 504 ('methods', "Methods")] | |
| 505 | |
| 506 detail = 0 | |
| 507 | |
| 508 def __init__(self, explorer, group, canvas): | |
| 509 Visage.__init__(self, explorer, group, canvas) | |
| 510 | |
| 511 class_identifier = self.explorer.klass.name | |
| 512 # XXX: include partial module name in class? | |
| 513 self.frame.set_label("%s (%s)" % (self.identifier, | |
| 514 class_identifier)) | |
| 515 | |
| 516 class InstanceAttributeWidget(AttributeWidget): | |
| 517 def getTextForLabel(self): | |
| 518 return "%s instance" % (self.explorer.klass.name,) | |
| 519 | |
| 520 | |
| 521 # Class | |
| 522 | |
| 523 class ExplorerClass(Explorer): | |
| 524 pass | |
| 525 | |
| 526 class ClassVisage(Visage): | |
| 527 propertyLabels = [("name", "Name"), | |
| 528 ("module", "Module"), | |
| 529 ("bases", "Bases")] | |
| 530 groupLabels = [('data', "Data"), | |
| 531 ('methods', "Methods")] | |
| 532 | |
| 533 def fill_properties(self, propValues): | |
| 534 Visage.fill_properties(self, propValues) | |
| 535 basesExplorer = propValues.get('bases')[1] | |
| 536 basesExplorer.view.callRemote("get_elements").addCallback(self.fill_base
s) | |
| 537 | |
| 538 def fill_bases(self, baseExplorers): | |
| 539 box = gtk.HBox() | |
| 540 for b in baseExplorers: | |
| 541 box.add(b.newAttributeWidget(self)) | |
| 542 row = self.propertyLabels.get('bases')[0] | |
| 543 self.subtable["properties"].attach(box, 1, 2, row, row+1) | |
| 544 box.show_all() | |
| 545 | |
| 546 class ClassAttributeWidget(AttributeWidget): | |
| 547 def getTextForLabel(self): | |
| 548 return self.explorer.name | |
| 549 | |
| 550 | |
| 551 # Function | |
| 552 | |
| 553 class ExplorerFunction(Explorer): | |
| 554 pass | |
| 555 | |
| 556 class FunctionAttributeWidget(AttributeWidget): | |
| 557 def getTextForLabel(self): | |
| 558 signature = self.explorer.signature | |
| 559 arglist = [] | |
| 560 for arg in xrange(len(signature)): | |
| 561 name = signature.name[arg] | |
| 562 hasDefault, default = signature.get_default(arg) | |
| 563 if hasDefault: | |
| 564 if default.explorerClass == "ExplorerImmutable": | |
| 565 default = default.value | |
| 566 else: | |
| 567 # XXX | |
| 568 pass | |
| 569 a = "%s=%s" % (name, default) | |
| 570 elif signature.is_varlist(arg): | |
| 571 a = "*%s" % (name,) | |
| 572 elif signature.is_keyword(arg): | |
| 573 a = "**%s" % (name,) | |
| 574 else: | |
| 575 a = name | |
| 576 arglist.append(a) | |
| 577 | |
| 578 return string.join(arglist, ", ") | |
| 579 | |
| 580 | |
| 581 # Method | |
| 582 | |
| 583 class ExplorerMethod(ExplorerFunction): | |
| 584 pass | |
| 585 | |
| 586 class MethodAttributeWidget(FunctionAttributeWidget): | |
| 587 pass | |
| 588 | |
| 589 class ExplorerBulitin(Explorer): | |
| 590 pass | |
| 591 | |
| 592 class ExplorerModule(Explorer): | |
| 593 pass | |
| 594 | |
| 595 class ExplorerSequence(Explorer): | |
| 596 pass | |
| 597 | |
| 598 | |
| 599 # Sequence | |
| 600 | |
| 601 class SequenceVisage(Visage): | |
| 602 propertyLabels = [('len', 'length')] | |
| 603 # XXX: add elements group | |
| 604 | |
| 605 class SequenceAttributeWidget(AttributeWidget): | |
| 606 def getTextForLabel(self): | |
| 607 # XXX: Differentiate between lists and tuples. | |
| 608 if self.explorer.len: | |
| 609 txt = "list of length %d" % (self.explorer.len,) | |
| 610 else: | |
| 611 txt = "[]" | |
| 612 return txt | |
| 613 | |
| 614 | |
| 615 # Mapping | |
| 616 | |
| 617 class ExplorerMapping(Explorer): | |
| 618 pass | |
| 619 | |
| 620 class MappingVisage(Visage): | |
| 621 propertyLabels = [('len', 'length')] | |
| 622 # XXX: add items group | |
| 623 | |
| 624 class MappingAttributeWidget(AttributeWidget): | |
| 625 def getTextForLabel(self): | |
| 626 if self.explorer.len: | |
| 627 txt = "dict with %d elements" % (self.explorer.len,) | |
| 628 else: | |
| 629 txt = "{}" | |
| 630 return txt | |
| 631 | |
| 632 class ExplorerImmutable(Explorer): | |
| 633 pass | |
| 634 | |
| 635 | |
| 636 # Immutable | |
| 637 | |
| 638 class ImmutableVisage(Visage): | |
| 639 def __init__(self, explorer, rootGroup, canvas): | |
| 640 Visage.__init__(self, explorer, rootGroup, canvas) | |
| 641 widget = explorer.newAttributeWidget(self) | |
| 642 self.container.add(widget) | |
| 643 self.container.show_all() | |
| 644 | |
| 645 class ImmutableAttributeWidget(AttributeWidget): | |
| 646 def getTextForLabel(self): | |
| 647 return repr(self.explorer.value) | |
| 648 | |
| 649 | |
| 650 #### misc. module definitions | |
| 651 | |
| 652 spelunkerClassTable = { | |
| 653 "ExplorerInstance": (InstanceVisage, InstanceAttributeWidget), | |
| 654 "ExplorerFunction": (None, FunctionAttributeWidget), | |
| 655 "ExplorerMethod": (None, MethodAttributeWidget), | |
| 656 "ExplorerImmutable": (ImmutableVisage, ImmutableAttributeWidget), | |
| 657 "ExplorerClass": (ClassVisage, ClassAttributeWidget), | |
| 658 "ExplorerSequence": (SequenceVisage, SequenceAttributeWidget), | |
| 659 "ExplorerMapping": (MappingVisage, MappingAttributeWidget), | |
| 660 } | |
| 661 GenericVisage = Visage | |
| 662 GenericAttributeWidget = AttributeWidget | |
| 663 | |
| 664 pb.setCopierForClassTree(sys.modules[__name__], | |
| 665 Explorer, 'twisted.manhole.explorer') | |
| OLD | NEW |