datacharts synchronised
authorandy
Wed, 07 Dec 2005 21:52:33 +0000
changeset 2547 9d13685212f2
parent 2546 664e4d46d0b8
child 2548 e94585f08b2f
datacharts synchronised
reportlab/graphics/renderbase.py
reportlab/graphics/shapes.py
reportlab/graphics/testshapes.py
reportlab/lib/attrmap.py
reportlab/lib/validators.py
--- a/reportlab/graphics/renderbase.py	Wed Dec 07 13:30:14 2005 +0000
+++ b/reportlab/graphics/renderbase.py	Wed Dec 07 21:52:33 2005 +0000
@@ -9,6 +9,7 @@
 __version__=''' $Id $ '''
 
 from reportlab.graphics.shapes import *
+from reportlab.lib.validators import DerivedValue
 from reportlab import rl_config
 
 def inverse(A):
@@ -55,47 +56,47 @@
     invert matrixes when you pop."""
     def __init__(self, defaults=None):
         # one stack to keep track of what changes...
-        self.__deltas = []
+        self._deltas = []
 
         # and another to keep track of cumulative effects.  Last one in
         # list is the current graphics state.  We put one in to simplify
         # loops below.
-        self.__combined = []
+        self._combined = []
         if defaults is None:
             defaults = STATE_DEFAULTS.copy()
         #ensure  that if we have a transform, we have a CTM
         if defaults.has_key('transform'):
             defaults['ctm'] = defaults['transform']
-        self.__combined.append(defaults)
+        self._combined.append(defaults)
 
     def push(self,delta):
         """Take a new state dictionary of changes and push it onto
         the stack.  After doing this, the combined state is accessible
         through getState()"""
 
-        newstate = self.__combined[-1].copy()
+        newstate = self._combined[-1].copy()
         for (key, value) in delta.items():
             if key == 'transform':  #do cumulative matrix
                 newstate['transform'] = delta['transform']
-                newstate['ctm'] = mmult(self.__combined[-1]['ctm'], delta['transform'])
+                newstate['ctm'] = mmult(self._combined[-1]['ctm'], delta['transform'])
                 #print 'statetracker transform = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['transform'])
                 #print 'statetracker ctm = (%0.2f, %0.2f, %0.2f, %0.2f, %0.2f, %0.2f)' % tuple(newstate['ctm'])
 
             else:  #just overwrite it
                 newstate[key] = value
 
-        self.__combined.append(newstate)
-        self.__deltas.append(delta)
+        self._combined.append(newstate)
+        self._deltas.append(delta)
 
     def pop(self):
         """steps back one, and returns a state dictionary with the
         deltas to reverse out of wherever you are.  Depending
         on your back end, you may not need the return value,
         since you can get the complete state afterwards with getState()"""
-        del self.__combined[-1]
-        newState = self.__combined[-1]
-        lastDelta = self.__deltas[-1]
-        del  self.__deltas[-1]
+        del self._combined[-1]
+        newState = self._combined[-1]
+        lastDelta = self._deltas[-1]
+        del  self._deltas[-1]
         #need to diff this against the last one in the state
         reverseDelta = {}
         #print 'pop()...'
@@ -112,19 +113,19 @@
 
     def getState(self):
         "returns the complete graphics state at this point"
-        return self.__combined[-1]
+        return self._combined[-1]
 
     def getCTM(self):
         "returns the current transformation matrix at this point"""
-        return self.__combined[-1]['ctm']
+        return self._combined[-1]['ctm']
 
     def __getitem__(self,key):
         "returns the complete graphics state value of key at this point"
-        return self.__combined[-1][key]
+        return self._combined[-1][key]
 
     def __setitem__(self,key,value):
         "sets the complete graphics state value of key to value"
-        self.__combined[-1][key] = value
+        self._combined[-1][key] = value
 
 def testStateTracker():
     print 'Testing state tracker'
@@ -179,6 +180,7 @@
 
     def __init__(self):
         self._tracker = StateTracker()
+        self._nodeStack = []   #track nodes visited
 
     def undefined(self, operation):
         raise ValueError, "%s operation not defined at superclass class=%s" %(operation, self.__class__)
@@ -195,7 +197,7 @@
             #bounding box
             if showBoundary: canvas.rect(x, y, drawing.width, drawing.height)
             canvas.saveState()
-            self.initState(x,y)
+            self.initState(x,y)  #this is the push()
             self.drawNode(drawing)
             self.pop()
             canvas.restoreState()
@@ -218,12 +220,32 @@
         # Undefined here, but with closer analysis probably can be handled in superclass
         self.undefined("drawNode")
 
+    def getStateValue(self, key):
+        """Return current state parameter for given key"""
+        currentState = self._tracker._combined[-1]
+        return currentState[key]
+    
+    def fillDerivedValues(self, node):
+        """Examine a node for any values which are Derived,
+        and replace them with their calculated values.
+        Generally things may look at the drawing or their
+        parent.
+        
+        """
+        for (key, value) in node.__dict__.items():
+            if isinstance(value, DerivedValue):
+                #just replace with default for key?
+                #print '    fillDerivedValues(%s)' % key
+                newValue = value.getValue(self, key)
+                #print '   got value of %s' % newValue
+                node.__dict__[key] = newValue
+
     def drawNodeDispatcher(self, node):
         """dispatch on the node's (super) class: shared code"""
-
+        
         canvas = getattr(self,'_canvas',None)
         # replace UserNode with its contents
-
+        
         try:
             node = _expandUserNode(node,canvas)
             if hasattr(node,'_canvas'):
@@ -232,6 +254,9 @@
                 node._canvas = canvas
                 ocanvas = None
 
+            self.fillDerivedValues(node)
+
+
             #draw the object, or recurse
             if isinstance(node, Line):
                 self.drawLine(node)
@@ -270,6 +295,9 @@
         canvas = getattr(self,'_canvas',None)
         for node in group.getContents():
             node = _expandUserNode(node,canvas)
+
+            #here is where we do derived values - this seems to get everything. Touch wood.            
+            self.fillDerivedValues(node)
             try:
                 if hasattr(node,'_canvas'):
                     ocanvas = 1
--- a/reportlab/graphics/shapes.py	Wed Dec 07 13:30:14 2005 +0000
+++ b/reportlab/graphics/shapes.py	Wed Dec 07 21:52:33 2005 +0000
@@ -569,8 +569,10 @@
         height = AttrMapValue(isNumber,desc="Drawing height in points."),
         canv = AttrMapValue(None),
         background = AttrMapValue(isValidChildOrNone,desc="Background widget for the drawing"),
-        hAlign = AttrMapValue(OneOf("LEFT", "RIGHT", "CENTER", "CENTRE"), desc="Alignment within parent document"),
-        vAlign = AttrMapValue(OneOf("TOP", "BOTTOM", "CENTER", "CENTRE"), desc="Alignment within parent document"),
+        hAlign = AttrMapValue(OneOf("LEFT", "RIGHT", "CENTER", "CENTRE"), desc="Horizontal alignment within parent document"),
+        vAlign = AttrMapValue(OneOf("TOP", "BOTTOM", "CENTER", "CENTRE"), desc="Vertical alignment within parent document"),
+        #AR temporary hack to track back up.
+        #fontName = AttrMapValue(isStringOrNone),
         renderScale = AttrMapValue(isNumber,desc="Global scaling for rendering"),
         )
 
@@ -584,6 +586,7 @@
         self.height = height
         self.hAlign = 'LEFT'
         self.vAlign = 'BOTTOM'
+        #self.fontName = 'Helvetica'
         self.renderScale = 1.0
 
     def _renderPy(self):
--- a/reportlab/graphics/testshapes.py	Wed Dec 07 13:30:14 2005 +0000
+++ b/reportlab/graphics/testshapes.py	Wed Dec 07 21:52:33 2005 +0000
@@ -196,7 +196,8 @@
 
     D.add(Rect(220, 150, 60, 30, 10, 10, fillColor=purple))  #round corners
 
-    D.add(String(10,50, 'Basic Shapes', fillColor=colors.black))
+    from reportlab.lib.validators import inherit
+    D.add(String(10,50, 'Basic Shapes', fillColor=colors.black, fontName=inherit))
 
     return D
 
@@ -443,6 +444,31 @@
     if maxx>400 or maxy>200: _,_,D = drawit(F,maxx,maxy)
     return D
 
+##def getDrawing14():
+##    """This tests inherited properties.  Each font should be as it says."""
+##    D = Drawing(400, 200)
+##    
+##    fontSize = 12
+##    D.fontName = 'Courier'
+##    
+##    g1 = Group(
+##            Rect(0, 0, 150, 20, fillColor=colors.yellow),
+##            String(5, 5, 'Inherited Courier', fontName=inherit, fontSize = fontSize)
+##            )
+##    D.add(g1)
+##
+##    g2 = Group(g1, transform = translate(25,25))
+##    D.add(g2)
+##
+##    g3 = Group(g2, transform = translate(25,25))
+##    D.add(g3)
+##
+##    g4 = Group(g3, transform = translate(25,25))
+##    D.add(g4)
+##
+##
+##    return D
+
 def getAllFunctionDrawingNames(doTTF=1):
     "Get a list of drawing function names from somewhere."
 
@@ -459,10 +485,10 @@
     return funcNames
 
 def _evalFuncDrawing(name, D, l=None, g=None):
-    try:
-        d = eval(name + '()', g or globals(), l or locals())
-    except:
-        d = getFailedDrawing(name)
+    #try:
+    d = eval(name + '()', g or globals(), l or locals())
+    #except:
+    #   d = getFailedDrawing(name)
     D.append((d, eval(name + '.__doc__'), name[3:]))
 
 def getAllTestDrawings(doTTF=1):
--- a/reportlab/lib/attrmap.py	Wed Dec 07 13:30:14 2005 +0000
+++ b/reportlab/lib/attrmap.py	Wed Dec 07 21:52:33 2005 +0000
@@ -3,7 +3,7 @@
 #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/attrmap.py
 __version__=''' $Id$ '''
 from UserDict import UserDict
-from reportlab.lib.validators import isAnything, _SequenceTypes
+from reportlab.lib.validators import isAnything, _SequenceTypes, DerivedValue
 from reportlab import rl_config
 
 class CallableValue:
@@ -74,12 +74,18 @@
     if rl_config.shapeChecking:
         map = obj._attrMap
         if map and name[0]!= '_':
-            try:
-                validate = map[name].validate
-                if not validate(value):
-                    raise AttributeError, "Illegal assignment of '%s' to '%s' in class %s" % (value, name, obj.__class__.__name__)
-            except KeyError:
-                raise AttributeError, "Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__)
+            #we always allow the inherited values; they cannot
+            #be checked until draw time.
+            if isinstance(value, DerivedValue):
+                #let it through
+                pass
+            else:            
+                try:
+                    validate = map[name].validate
+                    if not validate(value):
+                        raise AttributeError, "Illegal assignment of '%s' to '%s' in class %s" % (value, name, obj.__class__.__name__)
+                except KeyError:
+                    raise AttributeError, "Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__)
     obj.__dict__[name] = value
 
 def _privateAttrMap(obj,ret=0):
--- a/reportlab/lib/validators.py	Wed Dec 07 13:30:14 2005 +0000
+++ b/reportlab/lib/validators.py	Wed Dec 07 21:52:33 2005 +0000
@@ -156,7 +156,7 @@
 
 class _isValidChildOrNone(_isValidChild):
     def test(self,x):
-        return _isValidChild.test(self,x) or x is None
+        return x is None or _isValidChild.test(self,x)
 
 class _isCallable(Validator):
     def test(self, x):
@@ -239,6 +239,34 @@
     def test(self,x):
         return isinstance(x,self._klass)
 
+class DerivedValue:
+    """This is used for magic values which work themselves out.
+    An example would be an "inherit" property, so that one can have
+
+      drawing.chart.categoryAxis.labels.fontName = inherit
+
+    and pick up the value from the top of the drawing.
+    Validators will permit this provided that a value can be pulled
+    in which satisfies it.  And the renderer will have special
+    knowledge of these so they can evaluate themselves.
+    """
+    def getValue(self, renderer, attr):
+        """Override this.  The renderers will pass the renderer,
+        and the attribute name.  Algorithms can then backtrack up
+        through all the stuff the renderer provides, including
+        a correct stack of parent nodes."""
+        return None
+
+class Inherit(DerivedValue):
+    def __repr__(self):
+        return "inherit"
+    
+    def getValue(self, renderer, attr):
+        return renderer.getStateValue(attr)
+
+inherit = Inherit()
+    
+
 isAuto = Auto()
 isBoolean = _isBoolean()
 isString = _isString()