On 3/22/06, [EMAIL PROTECTED] <[EMAIL PROTECTED]> wrote:
> Does matplotlib work with AquaTerm as a backend?

Recently an AquaTerm backend was donated to matplotlib.  I haven't had
a chance to test or commit it to svn yet though.  I am attaching it
for anyone interested.

- Charlie
"""
An AquaTerm backend for Matplotlib and MacOS X
Vserion: 1.0b1
Author: Chris Murphy (christophermdmurphy at gmail d o t c o m)

See 'Installation' below before attempting to use this backend.

Features:
- Generate plots in MacOS X without using XWindows (X11)
- Save your plots as PDF files!
- Antialiased drawing built into AquaTerm (optional, adjust in AquaTerm Preferences)
- You get all of the great benefits of MacOS X and AquaTerm and Python!

Known Bugs:
- Clipping is causing some polygons to lose their face color
- Clipping is causing label and title text to get clipped
- Cannot change fonts
- No support for hex colors
- No support for Greyscale
- No support for images
- No support for dashed lines
- Draw arc is not optimized and is slow
- Resolution issues... some lines are getting drawn at subpixel linewidths. Aquaterm seems confused.
- PyObjC bridge does not allow passing of pointers
- No support for mathtext

Licensing:
I borrowed a lot of code and concepts from backend_template.py and some of the other backends.

Credits;
Thanks to Per Persson for authoring AquaTerm and providing tons of support. 
Thanks to John Hunter et. al. for authoring matplotlib.
Thanks to Tom Kornack for the idea of writing this backend.

Notes:
This is a fully functional AquaTerm backend. It is fully functional in
that you can select it as a backend with

  import matplotlib
  matplotlib.use('AQT')

Installtion:
This backend requires that you first install AquaTerm 1.0 for Mac OS X.
This backend also requires the installation of the PyObjC bridge.

Before you use backend_aqt, follow these steps:
Add 'AQT' to the switchyard in matplotlib/backends/__init__.py and
'AQT' to the _knownBackends dict in matplotlib/__init__.py and you're
off.  You can use your backend with

  import matplotlib
  matplotlib.use('AQT')
  from matplotlib.pylab import *
  plot([1,2,3])
  savefig('My Plot Title')

The files that are most relevant to backend_writers are

  matplotlib/backends/backend_your_backend.py
  matplotlib/backend_bases.py
  matplotlib/backends/__init__.py
  matplotlib/__init__.py
  matplotlib/_pylab_helpers.py
  
Naming Conventions

  * classes MixedUpperCase

  * varables lowerUpper

  * functions underscore_separated

"""


from matplotlib._pylab_helpers import Gcf
from matplotlib.backend_bases import RendererBase, GraphicsContextBase,\
     FigureManagerBase, FigureCanvasBase #,error_msg

from matplotlib.cbook import enumerate#, True, False #check for Python version. If <2.3 import True, False
from matplotlib.figure import Figure
from matplotlib.transforms import Bbox
from matplotlib.font_manager import fontManager
from matplotlib.ft2font import FT2Font
from matplotlib.mathtext import math_parse_s_ps, bakoma_fonts

import sys,os,math
from math import pi
from math import cos
from math import sin

import objc
from Foundation import *
objc.loadBundle("AquaTerm", globals(), bundle_path='/Library/Frameworks/AquaTerm.framework')

#####Globals#####
_fontd = {}
_clipd = {}

#Toggle Debugging Output
DEBUG = 1
RDEBUG = 1

#Constants that specify linecap styles.
AQTButtLineCapStyle=0
AQTRoundLineCapStyle=1
AQTSquareLineCapStyle=2
#Constants that specify horizontal and vertical alignment for labels. See #addLabel:atPoint:angle:align: for definitions and use.
AQTAlignLeft=0
AQTAlignCenter=1
AQTAlignRight=2
#Constants that specify vertical alignment for labels.
AQTAlignMiddle=0
AQTAlignBaseline=4
AQTAlignBottom=8
AQTAlignTop=16

if DEBUG: print "\n\n####################NEW PLOT##################\n\n"

#Create the adapater object in ObjC
adapter = AQTAdapter.alloc().init()

def error_msg_aqt(msg, *args):
    """
    Signal an error condition -- in a GUI, popup a error dialog
    """
    print >>sys.stderr, 'Error:', msg
    sys.exit()
    
class RendererAQT(RendererBase):
    """
    The renderer handles all the drawing primitives using a graphics
    context instance that controls the colors/styles
    """
    if DEBUG: print "\nStarting RendererAQT"
    
    def __init__(self, width, height, adapter):
    
        if RDEBUG: print "RendererAQT __init__"
        self.width = width
        self.height = height
        self._groupd = {}
        self._imaged = {} 
        self._adapter = adapter
        self._adapter.setPlotSize_((self.width, self.height))
        if RDEBUG: print "width(x) = %(width)s" % {'width' : self.width} + " pixels"
        if RDEBUG: print "height(y) = %(height)s" % {'height' : self.height} + " pixels\n"
        

    def get_canvas_width_height(self):
        'return the canvas width and height in display coords'
        if RDEBUG: print "get_canvas_width_height"
        return self.width, self.height

    def get_text_width_height(self, s, prop, ismath):
        """
        get the width and height in display coords of the string s
        with fontsize in points
        """
        if RDEBUG: print "get_text_width_height"
        #if RDEBUG: print "prop from get_text_width_height:"
        #if RDEBUG: print prop
        font = self._get_font(prop)
        font.set_text(s, 0.0)
        w, h = font.get_width_height()
        w /= 64.0  # convert from subpixels
        h /= 64.0
        if RDEBUG: print "width = %(w)s" % {'w' : w} + " pixels"
        if RDEBUG: print "height = %(h)s" % {'h' : h} + " pixels"
        return w, h
        
    def _get_font(self, prop):
        if RDEBUG: print "_get_font"
        key = hash(prop)
        font = _fontd.get(key)
        
        if font is None:
            fname = fontManager.findfont(prop)
            try:
                font = FT2Font(str(fname))
            except RuntimeError, msg:
                print >> sys.stderr, 'Could not load filename for text',fname
                return None
            else:
                _fontd[key] = font
            
        font.clear()
        size = prop.get_size_in_points()
        font.set_size(size, 72.0)
        return font 
                              
    def flipy(self):
        'return true if y small numbers are top for renderer'
        if RDEBUG: print "flipsy"
        return False
    

    def draw_mathtext(self, gc, x, y, s, prop, angle):
        """
        Draw the math text using matplotlib.mathtext
        """
        if RDEBUG: print 'RendererAQT.draw_mathtext'
        fontsize = prop.get_size_in_points()
        width, height, pswriter = math_parse_s_ps(s, 72, fontsize)
        
        if 0:
            print 'RendererAQT.draw_mathtext:'
            print 'x, y: ', x, y
            print 's: ', s
            self.draw_rectangle(gc, (1, 1, 1), x, y, width, height)

        thetext = pswriter.getvalue()
#        ps = """gsave
#%(x)f %(y)f translate
#%(angle)f rotate
#%(thetext)s
#grestore
#""" % locals()
#        self.draw_postscript(ps)

    def draw_text(self, gc, x, y, s, prop, angle, ismath=False):    
        """
        Render the matplotlib.text.Text instance at x, y in window
        coords using GraphicsContext gc
        """
        if RDEBUG: print "draw_text: %(text)s" % {'text' : s}
        #if RDEBUG: print "prop from draw_text:"
        #if RDEBUG: print prop
        
        if ismath:
            return self.draw_mathtext(gc, x, y, s, prop, angle)
        
        font = self._get_font(prop)
        fontsize = prop.get_size_in_points()
        fontname = font.get_sfnt()[(1,0,0,6)]
        #if RDEBUG: print "fontsize = %(size)s" % {'size' : fontsize} + " point"
        #if RDEBUG: print "fontname = %(name)s" % {'name' : fontname} + " point"
        #FixMe
        #self._adapter.setFontname_(fontname)
        self._adapter.setFontsize_(fontsize)
        self._adapter.addLabel_atPoint_angle_align_(s, (x,y), angle, (0|8))
        
        #Good place to beta test draw_arc
        #self.draw_arc(gc, None, 320, 240, 480, 10, 90, 225)
        
    def draw_arc(self, gcEdge, rgbFace, x, y, width, height, angle1, angle2):
        """
        Draw an arc centered at x,y with width and height and angles
        from 0.0 to 360.0.

        If rgbFace is not None, fill the rectangle with it.  gcEdge
        is a GraphicsContext instance
        """
        if RDEBUG: print "draw_arc"
        
        
        
        #radius
        r=width/2.0
        if width is 0: r=height/2.0
        
        xorigin=x
        yorigin=y
        
        a=range(360)
        x=range(360)
        y=range(360)
        i=angle1
        
        #Calculate all coordinate pairs in a circle, to draw arc
        for i in a:
        	radians = i*pi/179.5 
        	x[i] = xorigin + r*sin(radians)
        	y[i] = yorigin + r*cos(radians)
        	
        
        #Fill the arc with color rgbFace
        if rgbFace is not None:
        	r,g,b=rgbFace
        	self._adapter.setColorRed_green_blue_(r,g,b)
        	adapter.moveToVertexPoint_((x[0],y[0]))
        	i=1
        	for i in a:
        		adapter.addEdgeToVertexPoint_((x[i],y[i]))
        		
        #Get the edge color and linewidth	
        r,g,b=gcEdge.get_rgb()
        self._adapter.setColorRed_green_blue_(r,g,b)
        self._adapter.setLinewidth_(gcEdge.get_linewidth())

        #Draw the arc
        adapter.moveToPoint_((x[0], y[0]))

        #Only draw points up to angle2
        a=range(angle2)
        #print "angle1 = %(angle1)s" % {'angle1' : angle1} + " degrees"
        i=angle1
        #print "i = %(i)s" % {'i' : i} + " degrees"
        for i in a:
        	#print "i = %(i)s" % {'i' : i} + " degrees"
        	adapter.addLineToPoint_((x[i],y[i]))
        
        
    def draw_line(self, gc, x1, y1, x2, y2):
        """
        Draw a single line from x1,y1 to x2,y2
        """
        if RDEBUG: print "draw_line"
        self._adapter.moveToPoint_((x1, y1))
        self._adapter.addLineToPoint_((x2, y2))
        
        
    
    def draw_lines(self, gc, x, y):
        """
        x and y are equal length arrays, draw lines connecting each
        point in x, y
        """
        if RDEBUG: print "draw_lines"
        #test for x array size
        if len(x)==0: return
        if len(x)!=len(y): error_msg_aqt('x and y must be the same length')
        
        r=range(len(x))
        self._adapter.moveToPoint_((x[0], y[0]))
        
        for i in r:
        	#if RDEBUG: print "draw_lines loop"
        	self._adapter.addLineToPoint_((x[i], y[i]))
        	
        
    
    def draw_polygon(self, gcEdge, rgbFace, points):
        """
        Draw a polygon.  points is a len vertices tuple, each element
        giving the x,y coords a vertex.

        If rgbFace is not None, fill the rectangle with it.  gcEdge
        is a GraphicsContext instance
        """ 
        if RDEBUG: print "draw_polygon"
        
        if RDEBUG: print points
        if RDEBUG: print rgbFace
        
        
        a=len(points)
        j=range(a)
        
        self._adapter.moveToPoint_(points[0])
        

        for i in j:
        	r,g,b=gcEdge.get_rgb()
        	self._adapter.setColorRed_green_blue_(r,g,b)
        	self._adapter.setLinewidth_(gcEdge.get_linewidth())
        	self._adapter.addLineToPoint_(points[i])
        
        self._adapter.addLineToPoint_(points[0])
        
        if rgbFace is not None:
        	r,g,b=rgbFace
        	self._adapter.setColorRed_green_blue_(r,g,b)
                
        a=len(points)
        j=range(a)
 
        self._adapter.moveToVertexPoint_(points[0])
        
        for i in j:
        	self._adapter.addEdgeToVertexPoint_(points[i])


    def draw_rectangle(self, gcEdge, rgbFace, x, y, width, height):
        """
        Draw a rectangle at lower left x,y with width and height.

        If rgbFace is not None, fill the rectangle with it.  gcEdge
        is a GraphicsContext instance
        """
        if RDEBUG: print "draw_rectangle"
        	
        if rgbFace is not None:
        	if RDEBUG: print "draw_rectangle with color"
        	r,g,b=rgbFace
        	self._adapter.setColorRed_green_blue_(r,g,b)
        	self._adapter.addFilledRect_(((x, y), (width, height)))

		
		if RDEBUG: print "draw_rectangle add gcEdge to Rectangle"
        r,g,b=gcEdge.get_rgb()
        self._adapter.setColorRed_green_blue_(r,g,b)
        self._adapter.setLinewidth_(gcEdge.get_linewidth())
        self._adapter.moveToPoint_((x, y))
        x+=width
        self._adapter.addLineToPoint_((x, y))
        y+=height
        self._adapter.addLineToPoint_((x, y))
       	x-=width
        self._adapter.addLineToPoint_((x, y))
       	y-=height
        self._adapter.addLineToPoint_((x, y))

    def draw_point(self, gc, x, y):
        """
        Draw a single point at x,y
        """
        if RDEBUG: print "draw_point"
        #FixMe is there a better way to draw points using AquaTerm?
        self._adapter.moveToPoint_((x, y))
        self._adapter.addLineToPoint_((x+1, y+1))
        


    def new_gc(self):
        """
        Return an instance of a GraphicsContextAQT
        """
        if RDEBUG: print "\nnew_gc"
        return GraphicsContextAQT()


    def points_to_pixels(self, points):
        """
        convert points to display units.  Many imaging systems assume
        some value for pixels per inch.  Eg, suppose yours is 96 and
        dpi = 300.  Then points to pixels is
        """
        if RDEBUG: print "points to pixels"
        return 100/72.0 * 80/72.0 * points
 
    def draw_image(self, x, y, im, origin, bbox):
        """
        Draw the Image instance into the current axes; x is the
        distance in pixels from the left hand side of the canvas. y is
        the distance from the origin.  That is, if origin is upper, y
        is the distance from top.  If origin is lower, y is the
        distance from bottom
        """
        if RDEBUG: print "draw_image broken"
        #need to calculate size. x,y are not a location, they are a NSsize.
        self._adapter.addImageWithBitmap_size_bounds_(im, (x, y), bbox)
    
    

 
    def _render(self):
        """
        Render the plot in AquaTerm
        """
        if RDEBUG: print "Render the plot in AquaTerm"
        self._adapter.renderPlot()
        
    def clear(self):
        self._adapter.clearPlot()
       
class GraphicsContextAQT(GraphicsContextBase):
    """
    The graphics context provides the color, line styles, etc...  See
    the gtk and postscript backends for examples of mapping the
    graphics context attributes (cap styles, join styles, line widths,
    colors) to a particular backend.  In GTK this is done by wrapping
    a gtk.gdk.GC object and forwarding the appropriate calls to it
    using a dictionary mapping styles to gdk constants.  In
    Postscript, all the work is done by the renderer, mapping line
    styles to postscript calls.

    The base GraphicsContext stores colors as a RGB tuple on the unit
    interval, eg, (0.5, 0.0, 1.0).  You will probably need to map this
    to colors appropriate for your backend.  Eg, see the ColorManager
    class for the GTK backend.  If it's more appropriate to do the
    mapping at the renderer level (as in the postscript backend), you
    don't need to override any of the GC methods.  If it's more
    approritate to wrap an instance (as in the GTK backend) and do the
    mapping here, you'll need to override several of the setter
    methods.
    """
    
    if DEBUG: print "Starting GraphicsContextAQT"
    
    #line cap styles dictionary
    _capd = {
    'butt' : 0,
    'projecting' : 2,
    'round' : 1,
    }
    
    #dash pattern dictionary
    _dashd = {
    'dash0' : 0,
    'dash1' : 0,
    'dash2' : 0,
    'dash3' : 0,
    'dash4' : 0,
    'dash5' : 0,
    'dash6' : 0,
    'dash7' : 0,
    }

	#dash pattern arguments
	#_count = 0
	#_phase = 0
	
	
    
    def set_foreground(self, fg, isRGB=None):
        
        """
        Set the foreground color.  fg can be a matlab format string, a
        html hex color string, an rgb unit tuple, or a float between 0
        and 1.  In the latter case, grayscale is used.
        """
        #CM No Hex or Grayscale support yet!
        
        GraphicsContextBase.set_foreground(self, fg, isRGB)
        if isRGB is None:
        	if len(fg) > 1:
        		if fg is 'white': adapter.setColorRed_green_blue_(1.0, 1.0, 1.0)
        		if fg is 'black': adapter.setColorRed_green_blue_(0.0, 0.0, 0.0)
        	elif len(fg) is 3:
        			r,g,b=fg
        			adapter.setColorRed_green_blue_(r,g,b)
        	elif len(fg) is 1:
        		if fg is 'r': adapter.setColorRed_green_blue_(1.0, 0.0, 0.0)
        		if fg is 'g': adapter.setColorRed_green_blue_(0.0, 1.0, 0.0)
        		if fg is 'b': adapter.setColorRed_green_blue_(0.0, 0.0, 1.0)
        		if fg is 'c': adapter.setColorRed_green_blue_(0.0, 1.0, 1.0)
        		if fg is 'm': adapter.setColorRed_green_blue_(1.0, 0.0, 1.0)
        		if fg is 'y': adapter.setColorRed_green_blue_(1.0, 1.0, 0.0)
        		if fg is 'k': adapter.setColorRed_green_blue_(0.0, 0.0, 0.0)
        		if fg is 'w': adapter.setColorRed_green_blue_(1.0, 1.0, 1.0)
        		if fg <= 1: adapter.setColorRed_green_blue_(fg,0,0)
        
        #	elif fg.isdigit():
        #		adapter.setColorRed_green_blue_(fg, 0.0, 0.0)
        #	if DEBUG: print "color isRGB is None=%(fg)s" % {'fg' : fg}

        #elif isRGB is True:
        #	if DEBUG: print "color isRBGB is True=%(fg)s" % {'fg' : fg}
        
    def set_linewidth(self, lw):
        GraphicsContextBase.set_linewidth(self, lw)
        adapter.setLinewidth_(lw)
        #This produces a lot of output!
        #if DEBUG: print "linewidth = %(lw)s" % {'lw' : lw} + " pixels"
    
    def set_capstyle(self, cs):
    	GraphicsContextBase.set_capstyle(self, cs)
        'one of butt/round/square/none'        
        if cs is 'butt': adapter.setLineCapStyle_(0)
        if cs is 'round': adapter.setLineCapStyle_(1)
        if cs is 'projecting': adapter.setLineCapStyle_(2)
    
    def set_clip_rectangle(self, rect):
    	if RDEBUG: print "set_clip_rectangle = %(rect)s" % {'rect' : rect}
        #x,y,width,height = rect
        #adapter.setClipRect_(((x, y), (width, height)))

    def set_linestyle(self, style):
        """
        Set the linestyle to be one of ('solid', 'dashed', 'dashdot',
        'dotted').
        """
    	if RDEBUG: print "set_linestyle = %(style)s" % {'style' : style}
    	#if style is 'solid' : adapter.setLinestyleSolid_()
        #if style is 'dashed' : adapter.setLinestylePatternPython_count_phase_(dashd["dash1"], dashd["dash2"], dashd["dash3"], dashd["dash4"], _count, _phase)
        
    #def set_dashes(self, offset, dashes):
    	"""
    	Set the dash pattern.
    	The dash list is an even size list that gives the ink on, ink off in pixels.
    	AquaTerm is limited to a count of 8 as of v1.0b3.
    	"""
    #	if RDEBUG: print "set_dashes = offset: %(offset)s" % {'offset' : offset} + " dashes: %(dashes)s" % {'dashes' : dashes}
    #	if offset is not None:
     #   	_phase = offset
    	#else 
        #	_phase = 0
    	#if dashes is None:
        #	adapter.setLinestyleSolid_()  # switch dashes off
        #	_count = 0
        #	_phase = 0
    	#elif len(dashes) is 2:
        #	_count = 2
        #	_dashd["dashd1"], _dashd["dash2"] = dashes
    	#elif len(dashes) is 4:
        #	_count = 4
        #	_dashd["dashd1"], _dashd["dash2"], _dashd["dash3"], _dashd["dash4"] = dashes
    	#elif len(dashes) is 6:
        #	_count = 6
        #	_dashd["dashd1"], _dashd["dash2"], _dashd["dash3"], _dashd["dash4"], _dashd["dash5"], _dashd["dash6"] = dashes
    	#elif len(dashes) is 8:
        #	_count = 8
        #	_dashd["dashd1"], _dashd["dash2"], _dashd["dash3"], _dashd["dash4"], _dashd["dash5"], _dashd["dash6"], _dashd["dash7"], _dashd["dash8"] = dashes
        
        
########################################################################
#    
# The following functions and classes are for matlab compatibility
# mode (matplotlib.pylab) and implement window/figure managers,
# etc...
#
########################################################################

def draw_if_interactive():
    """
    This should be overriden in a windowing environment if drawing
    should be done in interactive python mode
    """
    #if DEBUG: print "Starting draw_if_interactive"

def show():
    """
    This is usually the last line of a matlab script and tells the
    backend that it is time to draw.  In interactive mode, this may be
    a do nothing func.  See the GTK backend for an example of how to
    handle interactive versus batch mode
    """
    if DEBUG: print "Starting show()"
    for manager in Gcf.get_all_fig_managers():
        manager.canvas.realize()


def new_figure_manager(num, *args, **kwargs):
    """
    Create a new figure manager instance
    """
    if DEBUG: print "Starting new_figure_manager"
    thisFig = Figure(*args, **kwargs)
    canvas = FigureCanvasAQT(thisFig)
    manager = FigureManagerAQT(canvas, num)
    return manager


class FigureCanvasAQT(FigureCanvasBase):
    """
    The canvas the figure renders into.  Calls the draw and print fig
    methods, creates the renderers, etc...

    Public attribute

      figure - A Figure instance
    """

    def draw(self):
        """
        Draw the figure using the renderer
        """
       
        if DEBUG: print "Starting draw in Figure CanvasAQT"
        
        width, height = self.figure.get_size_inches()
        if DEBUG: print "width(x) = %(width)s" % {'width' : width} + " inches"
        if DEBUG: print "height(y) = %(height)s" % {'height' : height} + " inches"
        #CM Convert Inches to pixels
        w = width*80
        h = height*80
        adapter.openPlotWithIndex_(1)
        renderer = RendererAQT(w, h, adapter)
        self.figure.draw(renderer)
        renderer._render()
        
    def print_figure(self, filename, dpi=80,
                     facecolor='w', edgecolor='w',
                     orientation='portrait'):

        """
        Render the figure to hardcopy.  Set the figure patch face and
        edge colors.  This is useful because some of the GUIs have a
        gray figure face color background and you'll probably want to
        override this on hardcopy
        """
        
        if DEBUG: print "Starting print_figure in Figure Canvas AQT"
        
        # set the new parameters
        origDPI = self.figure.dpi.get()
        if DEBUG: print "origDPI= %(origDPI)s" % {'origDPI' : origDPI} + "dpi"
        
        width, height = self.figure.get_size_inches()
        if DEBUG: print "width(x) = %(width)s" % {'width' : width} + " inches"
        if DEBUG: print "height(y) = %(height)s" % {'height' : height} + " inches"
        
        origfacecolor = self.figure.get_facecolor()
        origedgecolor = self.figure.get_edgecolor()
        
        #turning this off makes the image smaller but maintains the same window size:
        self.figure.dpi.set(dpi)
        self.figure.set_facecolor(facecolor)
        self.figure.set_edgecolor(edgecolor)        

		#CM Convert Inches to pixels
        w = width*100
        h = height*100
        
        adapter.openPlotWithIndex_(1)
        adapter.setPlotTitle_(filename)
        renderer = RendererAQT(w, h, adapter)
        
        self.figure.draw(renderer)
        renderer._render()
        # do something to save to hardcopy

        # restore the new params and redraw the screen if necessary
        #self.figure.dpi.set(origDPI)
        #self.figure.set_facecolor(origfacecolor)
        #self.figure.set_edgecolor(origedgecolor)
        #self.draw()
    
   
    def realize(self, *args):
        """
        This method will be called when the system is ready to draw,
        eg when a GUI window is realized
        """
        if DEBUG: print "Starting realize in Figure Canvas AQT"  
        self._isRealized = True  
        self.draw()
    
class FigureManagerAQT(FigureManagerBase):
    """
    Wrap everything up into a window for the matlab interface

    For non interactive backends, the base class does all the work
    """
    if DEBUG: print "Starting FigureManagerAQT(FigureManagerBase)"

########################################################################
#    
# Now just provide the standard names that backend.__init__ is expecting
# 
########################################################################


FigureManager = FigureManagerAQT
error_msg = error_msg_aqt
         

_______________________________________________
Pythonmac-SIG maillist  -  Pythonmac-SIG@python.org
http://mail.python.org/mailman/listinfo/pythonmac-sig

Reply via email to