~landscape/zope3/ztk-1.1.3

« back to all changes in this revision

Viewing changes to src/twisted/manhole/ui/spelunk_gnome.py

  • Committer: Andreas Hasenack
  • Date: 2009-07-20 17:49:16 UTC
  • Revision ID: andreas@canonical.com-20090720174916-g2tn6qmietz2hn0u
Revert twisted removal, it breaks several dozen tests [trivial]

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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_bases)
 
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')