~openerp-groupes/openobject-server/6.0-fix-setup-windows

« back to all changes in this revision

Viewing changes to bin/reportlab/graphics/widgetbase.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#Copyright ReportLab Europe Ltd. 2000-2004
 
2
#see license.txt for license details
 
3
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/widgetbase.py
 
4
__version__=''' $Id$ '''
 
5
import string
 
6
 
 
7
from reportlab.graphics import shapes
 
8
from reportlab import rl_config
 
9
from reportlab.lib import colors
 
10
from reportlab.lib.validators import *
 
11
from reportlab.lib.attrmap import *
 
12
 
 
13
 
 
14
class PropHolder:
 
15
    '''Base for property holders'''
 
16
 
 
17
    _attrMap = None
 
18
 
 
19
    def verify(self):
 
20
        """If the _attrMap attribute is not None, this
 
21
        checks all expected attributes are present; no
 
22
        unwanted attributes are present; and (if a
 
23
        checking function is found) checks each
 
24
        attribute has a valid value.  Either succeeds
 
25
        or raises an informative exception.
 
26
        """
 
27
 
 
28
        if self._attrMap is not None:
 
29
            for key in self.__dict__.keys():
 
30
                if key[0] <> '_':
 
31
                    msg = "Unexpected attribute %s found in %s" % (key, self)
 
32
                    assert self._attrMap.has_key(key), msg
 
33
            for (attr, metavalue) in self._attrMap.items():
 
34
                msg = "Missing attribute %s from %s" % (attr, self)
 
35
                assert hasattr(self, attr), msg
 
36
                value = getattr(self, attr)
 
37
                args = (value, attr, self.__class__.__name__)
 
38
                assert metavalue.validate(value), "Invalid value %s for attribute %s in class %s" % args
 
39
 
 
40
    if rl_config.shapeChecking:
 
41
        """This adds the ability to check every attribute assignment
 
42
        as it is made. It slows down shapes but is a big help when
 
43
        developing. It does not get defined if rl_config.shapeChecking = 0.
 
44
        """
 
45
 
 
46
        def __setattr__(self, name, value):
 
47
            """By default we verify.  This could be off
 
48
            in some parallel base classes."""
 
49
            validateSetattr(self,name,value)
 
50
 
 
51
 
 
52
    def getProperties(self,recur=1):
 
53
        """Returns a list of all properties which can be edited and
 
54
        which are not marked as private. This may include 'child
 
55
        widgets' or 'primitive shapes'.  You are free to override
 
56
        this and provide alternative implementations; the default
 
57
        one simply returns everything without a leading underscore.
 
58
        """
 
59
 
 
60
        from reportlab.lib.validators import isValidChild
 
61
 
 
62
        # TODO when we need it, but not before -
 
63
        # expose sequence contents?
 
64
 
 
65
        props = {}
 
66
        for name in self.__dict__.keys():
 
67
            if name[0:1] <> '_':
 
68
                component = getattr(self, name)
 
69
 
 
70
                if recur and isValidChild(component):
 
71
                    # child object, get its properties too
 
72
                    childProps = component.getProperties(recur=recur)
 
73
                    for (childKey, childValue) in childProps.items():
 
74
                        #key might be something indexed like '[2].fillColor'
 
75
                        #or simple like 'fillColor'; in the former case we
 
76
                        #don't need a '.' between me and my child.
 
77
                        if childKey[0] == '[':
 
78
                            props['%s%s' % (name, childKey)] = childValue
 
79
                        else:
 
80
                            props['%s.%s' % (name, childKey)] = childValue
 
81
                else:
 
82
                    props[name] = component
 
83
 
 
84
        return props
 
85
 
 
86
 
 
87
    def setProperties(self, propDict):
 
88
        """Permits bulk setting of properties.  These may include
 
89
        child objects e.g. "chart.legend.width = 200".
 
90
 
 
91
        All assignments will be validated by the object as if they
 
92
        were set individually in python code.
 
93
 
 
94
        All properties of a top-level object are guaranteed to be
 
95
        set before any of the children, which may be helpful to
 
96
        widget designers.
 
97
        """
 
98
 
 
99
        childPropDicts = {}
 
100
        for (name, value) in propDict.items():
 
101
            parts = string.split(name, '.', 1)
 
102
            if len(parts) == 1:
 
103
                #simple attribute, set it now
 
104
                setattr(self, name, value)
 
105
            else:
 
106
                (childName, remains) = parts
 
107
                try:
 
108
                    childPropDicts[childName][remains] = value
 
109
                except KeyError:
 
110
                    childPropDicts[childName] = {remains: value}
 
111
 
 
112
        # now assign to children
 
113
        for (childName, childPropDict) in childPropDicts.items():
 
114
            child = getattr(self, childName)
 
115
            child.setProperties(childPropDict)
 
116
 
 
117
 
 
118
    def dumpProperties(self, prefix=""):
 
119
        """Convenience. Lists them on standard output.  You
 
120
        may provide a prefix - mostly helps to generate code
 
121
        samples for documentation.
 
122
        """
 
123
 
 
124
        propList = self.getProperties().items()
 
125
        propList.sort()
 
126
        if prefix:
 
127
            prefix = prefix + '.'
 
128
        for (name, value) in propList:
 
129
            print '%s%s = %s' % (prefix, name, value)
 
130
 
 
131
 
 
132
class Widget(PropHolder, shapes.UserNode):
 
133
    """Base for all user-defined widgets.  Keep as simple as possible. Does
 
134
    not inherit from Shape so that we can rewrite shapes without breaking
 
135
    widgets and vice versa."""
 
136
 
 
137
    def _setKeywords(self,**kw):
 
138
        for k,v in kw.items():
 
139
            if not self.__dict__.has_key(k):
 
140
                setattr(self,k,v)
 
141
 
 
142
    def draw(self):
 
143
        msg = "draw() must be implemented for each Widget!"
 
144
        raise shapes.NotImplementedError, msg
 
145
 
 
146
    def demo(self):
 
147
        msg = "demo() must be implemented for each Widget!"
 
148
        raise shapes.NotImplementedError, msg
 
149
 
 
150
    def provideNode(self):
 
151
        return self.draw()
 
152
 
 
153
    def getBounds(self):
 
154
        "Return outer boundary as x1,y1,x2,y2.  Can be overridden for efficiency"
 
155
        return self.draw().getBounds()
 
156
 
 
157
_ItemWrapper={}
 
158
 
 
159
class TypedPropertyCollection(PropHolder):
 
160
    """A container with properties for objects of the same kind.
 
161
 
 
162
    This makes it easy to create lists of objects. You initialize
 
163
    it with a class of what it is to contain, and that is all you
 
164
    can add to it.  You can assign properties to the collection
 
165
    as a whole, or to a numeric index within it; if so it creates
 
166
    a new child object to hold that data.
 
167
 
 
168
    So:
 
169
        wedges = TypedPropertyCollection(WedgeProperties)
 
170
        wedges.strokeWidth = 2                # applies to all
 
171
        wedges.strokeColor = colors.red       # applies to all
 
172
        wedges[3].strokeColor = colors.blue   # only to one
 
173
 
 
174
    The last line should be taken as a prescription of how to
 
175
    create wedge no. 3 if one is needed; no error is raised if
 
176
    there are only two data points.
 
177
    """
 
178
 
 
179
    def __init__(self, exampleClass):
 
180
        #give it same validation rules as what it holds
 
181
        self.__dict__['_value'] = exampleClass()
 
182
        self.__dict__['_children'] = {}
 
183
 
 
184
    def __getitem__(self, index):
 
185
        try:
 
186
            return self._children[index]
 
187
        except KeyError:
 
188
            Klass = self._value.__class__
 
189
            if _ItemWrapper.has_key(Klass):
 
190
                WKlass = _ItemWrapper[Klass]
 
191
            else:
 
192
                class WKlass(Klass):
 
193
                    def __getattr__(self,name):
 
194
                        try:
 
195
                            return self.__class__.__bases__[0].__getattr__(self,name)
 
196
                        except:
 
197
                            if self._index and self._parent._children.has_key(self._index):
 
198
                                if self._parent._children[self._index].__dict__.has_key(name):
 
199
                                    return getattr(self._parent._children[self._index],name)
 
200
                            return getattr(self._parent,name)
 
201
                _ItemWrapper[Klass] = WKlass
 
202
 
 
203
            child = WKlass()
 
204
            child._parent = self
 
205
            if type(index) in (type(()),type([])):
 
206
                index = tuple(index)
 
207
                if len(index)>1:
 
208
                    child._index = tuple(index[:-1])
 
209
                else:
 
210
                    child._index = None
 
211
            else:
 
212
                child._index = None
 
213
            for i in filter(lambda x,K=child.__dict__.keys(): x in K,child._attrMap.keys()):
 
214
                del child.__dict__[i]
 
215
 
 
216
            self._children[index] = child
 
217
            return child
 
218
 
 
219
    def has_key(self,key):
 
220
        if type(key) in (type(()),type([])): key = tuple(key)
 
221
        return self._children.has_key(key)
 
222
 
 
223
    def __setitem__(self, key, value):
 
224
        msg = "This collection can only hold objects of type %s" % self._value.__class__.__name__
 
225
        assert isinstance(value, self._value.__class__), msg
 
226
 
 
227
    def __len__(self):
 
228
        return len(self._children.keys())
 
229
 
 
230
    def getProperties(self,recur=1):
 
231
        # return any children which are defined and whatever
 
232
        # differs from the parent
 
233
        props = {}
 
234
 
 
235
        for (key, value) in self._value.getProperties(recur=recur).items():
 
236
            props['%s' % key] = value
 
237
 
 
238
        for idx in self._children.keys():
 
239
            childProps = self._children[idx].getProperties(recur=recur)
 
240
            for (key, value) in childProps.items():
 
241
                if not hasattr(self,key) or getattr(self, key)<>value:
 
242
                    newKey = '[%s].%s' % (idx, key)
 
243
                    props[newKey] = value
 
244
        return props
 
245
 
 
246
    def setVector(self,**kw):
 
247
        for name, value in kw.items():
 
248
            for i in xrange(len(value)):
 
249
                setattr(self[i],name,value[i])
 
250
 
 
251
    def __getattr__(self,name):
 
252
        return getattr(self._value,name)
 
253
 
 
254
    def __setattr__(self,name,value):
 
255
        return setattr(self._value,name,value)
 
256
 
 
257
## No longer needed!
 
258
class StyleProperties(PropHolder):
 
259
    """A container class for attributes used in charts and legends.
 
260
 
 
261
    Attributes contained can be those for any graphical element
 
262
    (shape?) in the ReportLab graphics package. The idea for this
 
263
    container class is to be useful in combination with legends
 
264
    and/or the individual appearance of data series in charts.
 
265
 
 
266
    A legend could be as simple as a wrapper around a list of style
 
267
    properties, where the 'desc' attribute contains a descriptive
 
268
    string and the rest could be used by the legend e.g. to draw
 
269
    something like a color swatch. The graphical presentation of
 
270
    the legend would be its own business, though.
 
271
 
 
272
    A chart could be inspecting a legend or, more directly, a list
 
273
    of style properties to pick individual attributes that it knows
 
274
    about in order to render a particular row of the data. A bar
 
275
    chart e.g. could simply use 'strokeColor' and 'fillColor' for
 
276
    drawing the bars while a line chart could also use additional
 
277
    ones like strokeWidth.
 
278
    """
 
279
 
 
280
    _attrMap = AttrMap(
 
281
        strokeWidth = AttrMapValue(isNumber),
 
282
        strokeLineCap = AttrMapValue(isNumber),
 
283
        strokeLineJoin = AttrMapValue(isNumber),
 
284
        strokeMiterLimit = AttrMapValue(None),
 
285
        strokeDashArray = AttrMapValue(isListOfNumbersOrNone),
 
286
        strokeOpacity = AttrMapValue(isNumber),
 
287
        strokeColor = AttrMapValue(isColorOrNone),
 
288
        fillColor = AttrMapValue(isColorOrNone),
 
289
        desc = AttrMapValue(isString),
 
290
        )
 
291
 
 
292
    def __init__(self, **kwargs):
 
293
        "Initialize with attributes if any."
 
294
 
 
295
        for k, v in kwargs.items():
 
296
            setattr(self, k, v)
 
297
 
 
298
 
 
299
    def __setattr__(self, name, value):
 
300
        "Verify attribute name and value, before setting it."
 
301
        validateSetattr(self,name,value)
 
302
 
 
303
 
 
304
class TwoCircles(Widget):
 
305
    def __init__(self):
 
306
        self.leftCircle = shapes.Circle(100,100,20, fillColor=colors.red)
 
307
        self.rightCircle = shapes.Circle(300,100,20, fillColor=colors.red)
 
308
 
 
309
    def draw(self):
 
310
        return shapes.Group(self.leftCircle, self.rightCircle)
 
311
 
 
312
 
 
313
class Face(Widget):
 
314
    """This draws a face with two eyes.
 
315
 
 
316
    It exposes a couple of properties
 
317
    to configure itself and hides all other details.
 
318
    """
 
319
 
 
320
    _attrMap = AttrMap(
 
321
        x = AttrMapValue(isNumber),
 
322
        y = AttrMapValue(isNumber),
 
323
        size = AttrMapValue(isNumber),
 
324
        skinColor = AttrMapValue(isColorOrNone),
 
325
        eyeColor = AttrMapValue(isColorOrNone),
 
326
        mood = AttrMapValue(OneOf('happy','sad','ok')),
 
327
        )
 
328
 
 
329
    def __init__(self):
 
330
        self.x = 10
 
331
        self.y = 10
 
332
        self.size = 80
 
333
        self.skinColor = None
 
334
        self.eyeColor = colors.blue
 
335
        self.mood = 'happy'
 
336
 
 
337
    def demo(self):
 
338
        pass
 
339
 
 
340
    def draw(self):
 
341
        s = self.size  # abbreviate as we will use this a lot
 
342
        g = shapes.Group()
 
343
        g.transform = [1,0,0,1,self.x, self.y]
 
344
 
 
345
        # background
 
346
        g.add(shapes.Circle(s * 0.5, s * 0.5, s * 0.5, fillColor=self.skinColor))
 
347
 
 
348
        # left eye
 
349
        g.add(shapes.Circle(s * 0.35, s * 0.65, s * 0.1, fillColor=colors.white))
 
350
        g.add(shapes.Circle(s * 0.35, s * 0.65, s * 0.05, fillColor=self.eyeColor))
 
351
 
 
352
        # right eye
 
353
        g.add(shapes.Circle(s * 0.65, s * 0.65, s * 0.1, fillColor=colors.white))
 
354
        g.add(shapes.Circle(s * 0.65, s * 0.65, s * 0.05, fillColor=self.eyeColor))
 
355
 
 
356
        # nose
 
357
        g.add(shapes.Polygon(
 
358
            points=[s * 0.5, s * 0.6, s * 0.4, s * 0.3, s * 0.6, s * 0.3],
 
359
            fillColor=None))
 
360
 
 
361
        # mouth
 
362
        if self.mood == 'happy':
 
363
            offset = -0.05
 
364
        elif self.mood == 'sad':
 
365
            offset = +0.05
 
366
        else:
 
367
            offset = 0
 
368
 
 
369
        g.add(shapes.Polygon(
 
370
            points = [
 
371
                s * 0.3, s * 0.2, #left of mouth
 
372
                s * 0.7, s * 0.2, #right of mouth
 
373
                s * 0.6, s * (0.2 + offset), # the bit going up or down
 
374
                s * 0.4, s * (0.2 + offset) # the bit going up or down
 
375
                ],
 
376
            fillColor = colors.pink,
 
377
            strokeColor = colors.red,
 
378
            strokeWidth = s * 0.03
 
379
            ))
 
380
 
 
381
        return g
 
382
 
 
383
 
 
384
class TwoFaces(Widget):
 
385
    def __init__(self):
 
386
        self.faceOne = Face()
 
387
        self.faceOne.mood = "happy"
 
388
        self.faceTwo = Face()
 
389
        self.faceTwo.x = 100
 
390
        self.faceTwo.mood = "sad"
 
391
 
 
392
    def draw(self):
 
393
        """Just return a group"""
 
394
        return shapes.Group(self.faceOne, self.faceTwo)
 
395
 
 
396
    def demo(self):
 
397
        """The default case already looks good enough,
 
398
        no implementation needed here"""
 
399
        pass
 
400
 
 
401
class Sizer(Widget):
 
402
    "Container to show size of all enclosed objects"
 
403
 
 
404
    _attrMap = AttrMap(BASE=shapes.SolidShape,
 
405
        contents = AttrMapValue(isListOfShapes,desc="Contained drawable elements"),
 
406
        )
 
407
    def __init__(self, *elements):
 
408
        self.contents = []
 
409
        self.fillColor = colors.cyan
 
410
        self.strokeColor = colors.magenta
 
411
 
 
412
        for elem in elements:
 
413
            self.add(elem)
 
414
 
 
415
    def _addNamedNode(self,name,node):
 
416
        'if name is not None add an attribute pointing to node and add to the attrMap'
 
417
        if name:
 
418
            if name not in self._attrMap.keys():
 
419
                self._attrMap[name] = AttrMapValue(isValidChild)
 
420
            setattr(self, name, node)
 
421
 
 
422
    def add(self, node, name=None):
 
423
        """Appends non-None child node to the 'contents' attribute. In addition,
 
424
        if a name is provided, it is subsequently accessible by name
 
425
        """
 
426
        # propagates properties down
 
427
        if node is not None:
 
428
            assert isValidChild(node), "Can only add Shape or UserNode objects to a Group"
 
429
            self.contents.append(node)
 
430
            self._addNamedNode(name,node)
 
431
 
 
432
    def getBounds(self):
 
433
        # get bounds of each object
 
434
        if self.contents:
 
435
            b = []
 
436
            for elem in self.contents:
 
437
                b.append(elem.getBounds())
 
438
            return shapes.getRectsBounds(b)
 
439
        else:
 
440
            return (0,0,0,0)
 
441
 
 
442
    def draw(self):
 
443
        g = shapes.Group()
 
444
        (x1, y1, x2, y2) = self.getBounds()
 
445
        r = shapes.Rect(
 
446
            x = x1,
 
447
            y = y1,
 
448
            width = x2-x1,
 
449
            height = y2-y1,
 
450
            fillColor = self.fillColor,
 
451
            strokeColor = self.strokeColor
 
452
            )
 
453
        g.add(r)
 
454
        for elem in self.contents:
 
455
            g.add(elem)
 
456
        return g
 
457
 
 
458
def test():
 
459
    from reportlab.graphics.charts.piecharts import WedgeProperties
 
460
    wedges = TypedPropertyCollection(WedgeProperties)
 
461
    wedges.fillColor = colors.red
 
462
    wedges.setVector(fillColor=(colors.blue,colors.green,colors.white))
 
463
    print len(_ItemWrapper)
 
464
 
 
465
    d = shapes.Drawing(400, 200)
 
466
    tc = TwoCircles()
 
467
    d.add(tc)
 
468
    import renderPDF
 
469
    renderPDF.drawToFile(d, 'sample_widget.pdf', 'A Sample Widget')
 
470
    print 'saved sample_widget.pdf'
 
471
 
 
472
    d = shapes.Drawing(400, 200)
 
473
    f = Face()
 
474
    f.skinColor = colors.yellow
 
475
    f.mood = "sad"
 
476
    d.add(f, name='theFace')
 
477
    print 'drawing 1 properties:'
 
478
    d.dumpProperties()
 
479
    renderPDF.drawToFile(d, 'face.pdf', 'A Sample Widget')
 
480
    print 'saved face.pdf'
 
481
 
 
482
    d2 = d.expandUserNodes()
 
483
    renderPDF.drawToFile(d2, 'face_copy.pdf', 'An expanded drawing')
 
484
    print 'saved face_copy.pdf'
 
485
    print 'drawing 2 properties:'
 
486
    d2.dumpProperties()
 
487
 
 
488
 
 
489
if __name__=='__main__':
 
490
    test()