]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/floatcanvas/FloatCanvas.py
don't clear the tooltip unless there is one.
[wxWidgets.git] / wxPython / wx / lib / floatcanvas / FloatCanvas.py
index 0d5142fcabd12a2ecb49e53be25bba876c07d281..5a9b74186c2fb63b1cda56b3a5334ddf125a59da 100644 (file)
@@ -1,29 +1,34 @@
+from __future__ import division
 
 try:
-    from Numeric import array,asarray,Float,cos,pi,sum,minimum,maximum,Int32,zeros
+    from Numeric import array,asarray,Float,cos, sin, pi,sum,minimum,maximum,Int32,zeros, ones, concatenate, sqrt, argmin, power, absolute, matrixmultiply, transpose, sometrue, arange, hypot
 except ImportError:
-    from numarray import array, asarray, Float, cos, pi, sum, minimum, maximum, Int32, zeros
+    try:
+        from numarray import array, asarray, Float, cos, sin, pi, sum, minimum, maximum, Int32, zeros, concatenate, matrixmultiply, transpose, sometrue, arange, hypot
+    except ImportError:
+        raise ImportError("I could not import either Numeric or numarray")
 
 from time import clock, sleep
 
+import Resources # A file with icons, etc for FloatCanvas
+
 import wx
 
 import types
 import os        
 
-import Resources
-
 ## A global variable to hold the Pixels per inch that wxWindows thinks is in use
 ## This is used for scaling fonts.
-## This can't be computed on module __init__, because a wx.App might not have iniitalized yet.
-global ScreenPPI 
+## This can't be computed on module __init__, because a wx.App might not have initialized yet.
+global ScreenPPI
 
 ## a custom Exceptions:
 
-class FloatCanvasException(Exception):
+class FloatCanvasError(Exception):
     pass
 
-## All the mouse events
+## Create all the mouse events
+# I don't see a need for these two, but maybe some day!
 #EVT_FC_ENTER_WINDOW = wx.NewEventType()
 #EVT_FC_LEAVE_WINDOW = wx.NewEventType()
 EVT_FC_LEFT_DOWN = wx.NewEventType() 
@@ -41,6 +46,7 @@ EVT_FC_MOUSEWHEEL = wx.NewEventType()
 EVT_FC_ENTER_OBJECT = wx.NewEventType()
 EVT_FC_LEAVE_OBJECT = wx.NewEventType()
 
+##Create all mouse event binding functions
 #def EVT_ENTER_WINDOW( window, function ):
 #    window.Connect( -1, -1, EVT_FC_ENTER_WINDOW, function ) 
 #def EVT_LEAVE_WINDOW( window, function ):
@@ -68,7 +74,7 @@ def EVT_MOTION( window, function ):
 def EVT_MOUSEWHEEL( window, function ):
     window.Connect( -1, -1,EVT_FC_MOUSEWHEEL , function )
 
-class MouseEvent(wx.PyCommandEvent):
+class _MouseEvent(wx.PyCommandEvent):
 
     """
 
@@ -82,7 +88,7 @@ class MouseEvent(wx.PyCommandEvent):
 
     GetCoords() , which returns and (x,y) tuple in world coordinates.
 
-    Another differnce is that it is a CommandEvent, which propagates up
+    Another difference is that it is a CommandEvent, which propagates up
     the window hierarchy until it is handled.
 
     """
@@ -94,8 +100,9 @@ class MouseEvent(wx.PyCommandEvent):
         self._NativeEvent = NativeEvent
         self.Coords = Coords
     
-    def SetCoords(self,Coords):
-        self.Coords = Coords
+# I don't think this is used.
+#    def SetCoords(self,Coords):
+#        self.Coords = Coords
         
     def GetCoords(self):
         return self.Coords
@@ -104,74 +111,27 @@ class MouseEvent(wx.PyCommandEvent):
         #return eval(self.NativeEvent.__getattr__(name) )
         return getattr(self._NativeEvent, name)
 
-#### ColorGEnerator class is now obsolete. I'm using a python generator function instead.
-##class ColorGenerator:
+def _cycleidxs(indexcount, maxvalue, step):
 
-##    """
-
-##    An instance of this class generates a unique color each time
-##    GetNextColor() is called. Someday I will use a proper Python
-##    generator for this class.
-
-##    The point of this generator is for the hit-test bitmap, each object
-##    needs to be a unique color. Also, each system can be running a
-##    different number of colors, and it doesn't appear to be possible to
-##    have a wxMemDC with a different colordepth as the screen so this
-##    generates colors far enough apart that they can be distinguished on
-##    a 16bit screen. Anything less than 16bits won't work. It could, but
-##    I havn't written the code that way. You also wouldn't get many
-##    distict colors
-    
-##    """
+    """
+    Utility function used by _colorGenerator
 
-##    def __init__(self):
-##        import sys
-##        ## figure out the color depth of the screen
-##        ## for some bizare reason, thisdoesn't work on OS-X
-##        if sys.platform == 'darwin':
-##            depth = 24
-##        else:
-##            b = wx.EmptyBitmap(1,1)
-##            depth = b.GetDepth()
-##        self.r = 0
-##        self.g = 0
-##        self.b = 0
-##        if depth == 16:
-##            self.step = 8
-##        elif depth >= 24:
-##            self.step = 1
-##        else:
-##            raise FloatCanvasException("ColorGenerator does not work with depth = %s"%depth )
-
-##    def GetNextColor(self):
-##        step = self.step
-##        ##r,g,b = self.r,self.g,self.b
-##        self.r += step
-##        if self.r > 255:
-##            self.r = step
-##            self.g += step
-##            if self.g > 255:
-##                self.g = step
-##                self.b += step
-##                if self.b > 255:
-##                    ## fixme: this should be a derived exception
-##                    raise FloatCanvasException("Too many objects in colorgenerator for HitTest")
-##        return (self.r,self.g,self.b)
-
-##    def Reset(self):
-##        self.r = 0
-##        self.g = 0
-##        self.b = 0
-
-def cycleidxs(indexcount, maxvalue, step):
+    """
     if indexcount == 0:
         yield ()
     else:
         for idx in xrange(0, maxvalue, step):
-            for tail in cycleidxs(indexcount - 1, maxvalue, step):
+            for tail in _cycleidxs(indexcount - 1, maxvalue, step):
                 yield (idx, ) + tail
 
-def colorGenerator():
+def _colorGenerator():
+
+    """
+
+    Generates a seris of unique colors used to do hit-tests with the HIt
+    Test bitmap
+
+    """
     import sys
     if sys.platform == 'darwin':
         depth = 24
@@ -184,12 +144,13 @@ def colorGenerator():
         step = 1
     else:
         raise "ColorGenerator does not work with depth = %s" % depth
-    return cycleidxs(indexcount=3, maxvalue=256, step=step)
+    return _cycleidxs(indexcount=3, maxvalue=256, step=step)
 
 
-#### I don't know if the Set objects are useful, beyond the pointset object
-#### The problem is that when zoomed in, the BB is checked to see whether to draw the object.
-#### A Set object can defeat this
+#### I don't know if the Set objects are useful, beyond the pointset
+#### object. The problem is that when zoomed in, the BB is checked to see
+#### whether to draw the object.  A Set object can defeat this. One day
+#### I plan to write some custon C++ code to draw sets of objects
 
 ##class ObjectSetMixin:
 ##    """
@@ -246,15 +207,16 @@ def colorGenerator():
 ##        if length == 1:
 ##            self.Pens = self.Pens[0]
 
-
-
 class DrawObject:
     """
     This is the base class for all the objects that can be drawn.
 
+    One must subclass from this (and an assortment of Mixins) to create
+    a new DrawObject.
+
     """
 
-    def __init__(self,InForeground  = False):
+    def __init__(self, InForeground  = False, IsVisible = True):
         self.InForeground = InForeground
 
         self._Canvas = None
@@ -268,7 +230,14 @@ class DrawObject:
         self.HitFill = True
         self.MinHitLineWidth = 3
         self.HitLineWidth = 3 ## this gets re-set by the subclasses if necessary
+
+        self.Brush = None
+        self.Pen = None
+
+        self.FillStyle = "Solid"
         
+        self.Visible = IsVisible
+
     # I pre-define all these as class variables to provide an easier
     # interface, and perhaps speed things up by caching all the Pens
     # and Brushes, although that may not help, as I think wx now
@@ -326,7 +295,7 @@ class DrawObject:
             self._Canvas.MakeNewHTdc()
         if not self.HitColor:
             if not self._Canvas.HitColorGenerator:
-                self._Canvas.HitColorGenerator = colorGenerator()
+                self._Canvas.HitColorGenerator = _colorGenerator()
                 self._Canvas.HitColorGenerator.next() # first call to prevent the background color from being used.
             self.HitColor = self._Canvas.HitColorGenerator.next()
             self.SetHitPen(self.HitColor,self.HitLineWidth)
@@ -336,7 +305,6 @@ class DrawObject:
             self._Canvas.MakeHitDict()
         self._Canvas.HitDict[Event][self.HitColor] = (self) # put the object in the hit dict, indexed by it's color
 
-
     def UnBindAll(self):
         ## fixme: this only removes one from each list, there could be more.
         if self._Canvas.HitDict:
@@ -347,9 +315,11 @@ class DrawObject:
                     pass
         self.HitAble = False
 
+
     def SetBrush(self,FillColor,FillStyle):
         if FillColor is None or FillStyle is None:
             self.Brush = wx.TRANSPARENT_BRUSH
+            ##fixme: should I really re-set the style?
             self.FillStyle = "Transparent"
         else:
             self.Brush = self.BrushList.setdefault( (FillColor,FillStyle),  wx.Brush(FillColor,self.FillStyleList[FillStyle] ) )
@@ -371,7 +341,7 @@ class DrawObject:
         if not self.HitLine:
             self.HitPen = wx.TRANSPARENT_PEN
         else:
-             self.HitPen = self.PenList.setdefault( (HitColor, "solid", LineWidth),  wx.Pen(HitColor, LineWidth, self.LineStyleList["Solid"]) )
+            self.HitPen = self.PenList.setdefault( (HitColor, "solid", self.HitLineWidth),  wx.Pen(HitColor, self.HitLineWidth, self.LineStyleList["Solid"]) )
 
     def PutInBackground(self):
         if self._Canvas and self.InForeground:
@@ -387,6 +357,61 @@ class DrawObject:
             self._Canvas._BackgroundDirty = True
             self.InForeground = True
 
+    def Hide(self):
+        self.Visible = False
+
+    def Show(self):
+        self.Visible = True
+
+class ColorOnlyMixin:
+    """
+
+    Mixin class for objects that have just one color, rather than a fill
+    color and line color
+
+    """
+
+    def SetColor(self, Color):
+        self.SetPen(Color,"Solid",1)
+        self.SetBrush(Color,"Solid")
+
+    SetFillColor = SetColor # Just to provide a consistant interface 
+
+class LineOnlyMixin:
+    """
+
+    Mixin class for objects that have just one color, rather than a fill
+    color and line color
+
+    """
+
+    def SetLineColor(self, LineColor):
+        self.LineColor = LineColor
+        self.SetPen(LineColor,self.LineStyle,self.LineWidth)
+
+    def SetLineStyle(self, LineStyle):
+        self.LineStyle = LineStyle
+        self.SetPen(self.LineColor,LineStyle,self.LineWidth)
+
+    def SetLineWidth(self, LineWidth):
+        self.LineWidth = LineWidth
+        self.SetPen(self.LineColor,self.LineStyle,LineWidth)
+
+class LineAndFillMixin(LineOnlyMixin):
+    """
+
+    Mixin class for objects that have both a line and a fill color and
+    style.
+
+    """
+    def SetFillColor(self, FillColor):
+        self.FillColor = FillColor
+        self.SetBrush(FillColor, self.FillStyle)
+
+    def SetFillStyle(self, FillStyle):
+        self.FillStyle = FillStyle
+        self.SetBrush(self.FillColor,FillStyle)
+    
 class XYObjectMixin:
     """
 
@@ -406,9 +431,26 @@ class XYObjectMixin:
         Delta = asarray(Delta, Float)
         self.XY += Delta
         self.BoundingBox = self.BoundingBox + Delta
+        
         if self._Canvas:
             self._Canvas.BoundingBoxDirty = True      
 
+    def CalcBoundingBox(self):
+        ## This may get overwritten in some subclasses
+        self.BoundingBox = array( (self.XY, self.XY), Float )
+
+    def SetPoint(self, xy):
+        xy = array( xy, Float)
+        xy.shape = (2,)
+        Delta = xy - self.XY
+        
+        self.XY = xy
+        self.BoundingBox = self.BoundingBox + Delta
+
+        #self.CalcBoundingBox()
+        if self._Canvas:
+            self._Canvas.BoundingBoxDirty = True     
+
 class PointsObjectMixin:
     """
 
@@ -417,12 +459,15 @@ class PointsObjectMixin:
 
     """
 
-## This is code for the XYMixin object, it needs to be adapeted and tested.
+
+## This is code for the PointsObjectMixin object, it needs to be adapted and tested.
+## Is the neccesary at all: you can always do:
+##    Object.SetPoints( Object.Points + delta, copy = False)    
 ##    def Move(self, Delta ):
 ##        """
 
 ##        Move(Delta): moves the object by delta, where delta is an (dx,
-##        dy) pair. Ideally a Numpy array or shape (2,)
+##        dy) pair. Ideally a Numpy array of shape (2,)
 
 ##        """
         
@@ -432,15 +477,38 @@ class PointsObjectMixin:
 ##        if self._Canvas:
 ##            self._Canvas.BoundingBoxDirty = True      
 
-    def SetPoints(self,Points):
-        self.Points = Points
-        self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
+    def CalcBoundingBox(self):
+        self.BoundingBox = array(((min(self.Points[:,0]),
+                                   min(self.Points[:,1]) ),
+                                  (max(self.Points[:,0]),
+                                   max(self.Points[:,1]) ) ), Float )
         if self._Canvas:
             self._Canvas.BoundingBoxDirty = True
 
+    def SetPoints(self, Points, copy = True):
+        """
+        Sets the coordinates of the points of the object to Points (NX2 array).
+
+        By default, a copy is made, if copy is set to False, a reference
+        is used, iff Points is a NumPy array of Floats. This allows you
+        to change some or all of the points without making any copies.
+
+        For example:
+
+        Points = Object.Points
+        Points += (5,10) # shifts the points 5 in the x dir, and 10 in the y dir.
+        Object.SetPoints(Points, False) # Sets the points to the same array as it was
+        
+        """
+        if copy:
+            self.Points = array(Points, Float)
+            self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point
+        else:
+            self.Points = asarray(Points, Float)
+        self.CalcBoundingBox()
 
      
-class Polygon(DrawObject,PointsObjectMixin):
+class Polygon(DrawObject,PointsObjectMixin,LineAndFillMixin):
 
     """
 
@@ -450,6 +518,9 @@ class Polygon(DrawObject,PointsObjectMixin):
     x-coordinate of point N and Points[N,1] is the y-coordinate for
     arrays.
 
+    The other parameters specify various properties of the Polygon, and
+    should be self explanatory.
+
     """
     def __init__(self,
                  Points,
@@ -461,7 +532,7 @@ class Polygon(DrawObject,PointsObjectMixin):
                  InForeground = False):
         DrawObject.__init__(self,InForeground)
         self.Points = array(Points,Float) # this DOES need to make a copy
-        self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
+        self.CalcBoundingBox()
 
         self.LineColor = LineColor
         self.LineStyle = LineStyle
@@ -475,7 +546,7 @@ class Polygon(DrawObject,PointsObjectMixin):
         self.SetBrush(FillColor,FillStyle)
 
     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel = None, HTdc=None):
-        Points = WorldToPixel(self.Points)
+        Points = WorldToPixel(self.Points)#.tolist()
         dc.SetPen(self.Pen)
         dc.SetBrush(self.Brush)
         dc.DrawPolygon(Points)
@@ -517,13 +588,14 @@ class Polygon(DrawObject,PointsObjectMixin):
 ##        dc.DrawLineList(Points,self.Pens)
  
 
-class Line(DrawObject,PointsObjectMixin):
+class Line(DrawObject,PointsObjectMixin,LineOnlyMixin):
     """
-    The Line class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
-    so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate
-    or  Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays.
 
-    It will draw a straight line if there are two points, and a polyline if there are more than two.
+    The Line class takes a list of 2-tuples, or a NX2 NumPy Float array
+    of point coordinates.
+
+    It will draw a straight line if there are two points, and a polyline
+    if there are more than two.
 
     """
     def __init__(self,Points,
@@ -535,7 +607,7 @@ class Line(DrawObject,PointsObjectMixin):
 
 
         self.Points = array(Points,Float)
-        self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
+        self.CalcBoundingBox()
 
         self.LineColor = LineColor
         self.LineStyle = LineStyle
@@ -554,6 +626,99 @@ class Line(DrawObject,PointsObjectMixin):
             HTdc.SetPen(self.HitPen)
             HTdc.DrawLines(Points)
 
+class Arrow(DrawObject,XYObjectMixin,LineOnlyMixin):
+    """
+
+    Arrow(XY, # coords of origin of arrow (x,y)
+          Length, # length of arrow in pixels
+          theta, # angle of arrow in degrees: zero is straight up
+                 # angle is to the right
+          LineColor = "Black",
+          LineStyle = "Solid",
+          LineWidth    = 1, 
+          ArrowHeadSize = 4,
+          ArrowHeadAngle = 45,
+          InForeground = False):
+
+    It will draw an arrow , starting at the point, (X,Y) pointing in
+    direction, theta.
+
+
+    """
+    def __init__(self,
+                 XY,
+                 Length,
+                 Direction,
+                 LineColor = "Black",
+                 LineStyle = "Solid",
+                 LineWidth    = 2, # pixels
+                 ArrowHeadSize = 8, # pixels
+                 ArrowHeadAngle = 30, # degrees
+                 InForeground = False):
+
+        DrawObject.__init__(self, InForeground)
+
+        self.XY = array(XY, Float)
+        self.XY.shape = (2,) # Make sure it is a 1X2 array, even if there is only one point
+        self.Length = Length
+        self.Direction = float(Direction)
+        self.ArrowHeadSize = ArrowHeadSize 
+        self.ArrowHeadAngle = float(ArrowHeadAngle)        
+
+        self.CalcArrowPoints()
+        self.CalcBoundingBox()
+
+        self.LineColor = LineColor
+        self.LineStyle = LineStyle
+        self.LineWidth = LineWidth
+
+        self.SetPen(LineColor,LineStyle,LineWidth)
+
+        ##fixme: How should the HitTest be drawn?
+        self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
+
+    def SetDirection(self, Direction):
+        self.Direction = float(Direction)
+        self.CalcArrowPoints()
+        
+    def SetLength(self, Length):
+        self.Length = Length
+        self.CalcArrowPoints()
+
+    def SetLengthDirection(self, Length, Direction):
+        self.Direction = float(Direction)
+        self.Length = Length
+        self.CalcArrowPoints()
+        
+    def SetLength(self, Length):
+        self.Length = Length
+        self.CalcArrowPoints()
+
+    ## fixme: cache this?
+    def CalcArrowPoints(self):
+        L = self.Length
+        S = self.ArrowHeadSize
+        phi = self.ArrowHeadAngle * pi / 360
+        theta = (self.Direction-90.0) * pi / 180
+        ArrowPoints = array( ( (0, L, L - S*cos(phi),L, L - S*cos(phi) ),
+                               (0, 0, S*sin(phi),    0, -S*sin(phi)    ) ),
+                             Float )
+        RotationMatrix = array( ( ( cos(theta), -sin(theta) ),
+                                  ( sin(theta), cos(theta) ) ),
+                                Float
+                                )
+        ArrowPoints = matrixmultiply(RotationMatrix, ArrowPoints)
+        self.ArrowPoints = transpose(ArrowPoints)
+
+    def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+        dc.SetPen(self.Pen)
+        xy = WorldToPixel(self.XY)
+        ArrowPoints = xy + self.ArrowPoints
+        dc.DrawLines(ArrowPoints)
+        if HTdc and self.HitAble:
+            HTdc.SetPen(self.HitPen)
+            HTdc.DrawLines(ArrowPoints)
+
 ##class LineSet(DrawObject, ObjectSetMixin):
 ##    """
 ##    The LineSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
@@ -585,18 +750,24 @@ class Line(DrawObject,PointsObjectMixin):
 ##        Points.shape = (-1,4)
 ##        dc.DrawLineList(Points,self.Pens)
 
-class PointSet(DrawObject):
+class PointSet(DrawObject,PointsObjectMixin, ColorOnlyMixin):
     """
-    The PointSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
-    so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate
-    or  Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays.
 
-    Each point will be drawn the same color and Diameter. The Diameter is in screen points,
-    not world coordinates.
+    The PointSet class takes a list of 2-tuples, or a NX2 NumPy array of
+    point coordinates.
+
+    If Points is a sequence of tuples: Points[N][0] is the x-coordinate of
+    point N and Points[N][1] is the y-coordinate.
 
-    At this point, the hit-test code does not distingish between the
-    points, you will only know that one of the poins got hit, not which
-    one.
+    If Points is a NumPy array: Points[N,0] is the x-coordinate of point
+    N and Points[N,1] is the y-coordinate for arrays.
+
+    Each point will be drawn the same color and Diameter. The Diameter
+    is in screen pixels, not world coordinates.
+
+    The hit-test code does not distingish between the points, you will
+    only know that one of the points got hit, not which one. You can use
+    PointSet.FindClosestPoint(WorldPoint) to find out which one
 
     In the case of points, the HitLineWidth is used as diameter.
 
@@ -606,27 +777,30 @@ class PointSet(DrawObject):
 
         self.Points = array(Points,Float)
         self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point
-        self.BoundingBox = array(((min(self.Points[:,0]),
-                                   min(self.Points[:,1])),
-                                  (max(self.Points[:,0]),
-                                   max(self.Points[:,1]))),Float)
-
-        self.Color = Color
+        self.CalcBoundingBox()
         self.Diameter = Diameter
 
         self.HitLineWidth = self.MinHitLineWidth
-        self.SetPen(Color,"Solid",1)
-        self.SetBrush(Color,"Solid")
+        self.SetColor(Color)
 
-    def SetPoints(self,Points):
-        self.Points = array(Points, Float)
-        self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point
-        self.BoundingBox = array(((min(self.Points[:,0]),
-                                   min(self.Points[:,1]) ),
-                                  (max(self.Points[:,0]),
-                                   max(self.Points[:,1]) ) ) )
-        if self._Canvas:
-            self._Canvas.BoundingBoxDirty = True
+    def SetDiameter(self,Diameter):
+            self.Diameter = Diameter
+            
+    def FindClosestPoint(self, XY):
+        """
+        
+        Returns the index of the closest point to the point, XY, given
+        in World coordinates. It's essentially random which you get if
+        there are more than one that are the same.
+
+        This can be used to figure out which point got hit in a mouse
+        binding callback, for instance. It's a lot faster that using a
+        lot of separate points.
+
+        """
+        d = self.Points - XY
+        return argmin(hypot(d[:,0],d[:,1]))
+    
 
     def DrawD2(self, dc, Points):
         # A Little optimization for a diameter2 - point
@@ -645,58 +819,124 @@ class PointSet(DrawObject):
         else:
             dc.SetBrush(self.Brush)
             radius = int(round(self.Diameter/2))
-            for xy in Points:
-                dc.DrawEllipsePointSize( (xy - radius), (self.Diameter, self.Diameter) )
+            ##fixme: I really should add a DrawCircleList to wxPython
+            if len(Points) > 100:
+                xy = Points
+                xywh = concatenate((xy-radius, ones(xy.shape) * self.Diameter ), 1 )
+                dc.DrawEllipseList(xywh)
+            else:
+                for xy in Points:
+                    dc.DrawCircle(xy[0],xy[1], radius)
         if HTdc and self.HitAble:
             HTdc.SetPen(self.HitPen)
+            HTdc.SetBrush(self.HitBrush)
             if self.Diameter <= 1:
                 HTdc.DrawPointList(Points)
             elif self.Diameter <= 2:
                 self.DrawD2(HTdc, Points)
+            else:
+                if len(Points) > 100:
+                    xy = Points
+                    xywh = concatenate((xy-radius, ones(xy.shape) * self.Diameter ), 1 )
+                    HTdc.DrawEllipseList(xywh)
+                else:
+                    for xy in Points:
+                        HTdc.DrawCircle(xy[0],xy[1], radius)
+
+class Point(DrawObject,XYObjectMixin,ColorOnlyMixin):
+    """
+    
+    The Point class takes a 2-tuple, or a (2,) NumPy array of point
+    coordinates.
+
+    The Diameter is in screen points, not world coordinates, So the
+    Bounding box is just the point, and doesn't include the Diameter.
+
+    The HitLineWidth is used as diameter for the
+    Hit Test.
+
+    """
+    def __init__(self, XY, Color = "Black", Diameter =  1, InForeground = False):
+        DrawObject.__init__(self, InForeground)
+        self.XY = array(XY, Float)
+        self.XY.shape = (2,) # Make sure it is a 1X2 array, even if there is only one point
+        self.CalcBoundingBox()
+        self.SetColor(Color)
+        self.Diameter = Diameter
+
+        self.HitLineWidth = self.MinHitLineWidth
+
+    def SetDiameter(self,Diameter):
+            self.Diameter = Diameter
+
+
+    def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+        dc.SetPen(self.Pen)
+        xy = WorldToPixel(self.XY)
+        if self.Diameter <= 1:
+            dc.DrawPoint(xy[0], xy[1])
+        else:
+            dc.SetBrush(self.Brush)
+            radius = int(round(self.Diameter/2))
+            dc.DrawCircle(xy[0],xy[1], radius)
+        if HTdc and self.HitAble:
+            HTdc.SetPen(self.HitPen)
+            if self.Diameter <= 1:
+                HTdc.DrawPoint(xy[0], xy[1])
             else:
                 HTdc.SetBrush(self.HitBrush)
-                radius = int(round(self.Diameter/2))
-                for xy in Points:
-                    HTdc.DrawEllipsePointSize(  (xy - radius), (self.Diameter, self.Diameter) )
-            
-#### Does anyone need this?                    
-##class Dot(DrawObject):
-##    """
-##    The Dot class takes an x.y coordinate pair, and the Diameter of the circle.
-##    The Diameter is in pixels, so it won't change with zoom.
+                HTdc.DrawCircle(xy[0],xy[1], radius)
 
-##    Also Fill and line data
+class SquarePoint(DrawObject,XYObjectMixin,ColorOnlyMixin):
+    """
+    
+    The SquarePoint class takes a 2-tuple, or a (2,) NumPy array of point
+    coordinates. It produces a square dot, centered on Point
 
-##    """
-##    def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,InForeground = False):
-##        DrawObject.__init__(self,InForeground)
+    The Size is in screen points, not world coordinates, so the
+    Bounding box is just the point, and doesn't include the Size.
 
-##        self.X = x
-##        self.Y = y
-##        self.Diameter = Diameter
-##        # NOTE: the bounding box does not include the diameter of the dot, as that is in pixel coords.
-##        # If this is  a problem, perhaps you should use a circle, instead!
-##        self.BoundingBox = array(((x,y),(x,y)),Float)
+    The HitLineWidth is used as diameter for the
+    Hit Test.
 
-##        self.LineColor = LineColor
-##        self.LineStyle = LineStyle
-##        self.LineWidth = LineWidth
-##        self.FillColor = FillColor
-##        self.FillStyle = FillStyle
+    """
+    def __init__(self, Point, Color = "Black", Size =  4, InForeground = False):
+        DrawObject.__init__(self, InForeground)
+        self.XY = array(Point, Float)
+        self.XY.shape = (2,) # Make sure it is a 1X2 array, even if there is only one point
+        self.CalcBoundingBox()
+        self.SetColor(Color)
+        self.Size = Size
 
-##        self.SetPen(LineColor,LineStyle,LineWidth)
-##        self.SetBrush(FillColor,FillStyle)
+        self.HitLineWidth = self.MinHitLineWidth
 
-##    def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
-##    #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
-##        dc.SetPen(self.Pen)
-##        dc.SetBrush(self.Brush)
-##        radius = int(round(self.Diameter/2))
-##        (X,Y) = WorldToPixel((self.X,self.Y))
-##        dc.DrawEllipse((X - radius), (Y - radius), self.Diameter, self.Diameter)
-
-class RectEllipse(DrawObject, XYObjectMixin):
-    def __init__(self,x,y,width,height,
+    def SetSize(self,Size):
+            self.Size = Size
+
+    def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+        Size = self.Size
+        dc.SetPen(self.Pen)
+        xc,yc = WorldToPixel(self.XY)
+        
+        if self.Size <= 1:
+            dc.DrawPoint(xc, yc)
+        else:
+            x = xc - Size/2.0
+            y = yc - Size/2.0
+            dc.SetBrush(self.Brush)
+            dc.DrawRectangle(x, y, Size, Size)
+        if HTdc and self.HitAble:
+            HTdc.SetPen(self.HitPen)
+            if self.Size <= 1:
+                HTdc.DrawPoint(xc, xc)
+            else:
+                HTdc.SetBrush(self.HitBrush)
+                HTdc.DrawRectangle(x, y, Size, Size)
+
+class RectEllipse(DrawObject, XYObjectMixin, LineAndFillMixin):
+    def __init__(self, XY, WH,
                  LineColor = "Black",
                  LineStyle = "Solid",
                  LineWidth    = 1,
@@ -706,9 +946,11 @@ class RectEllipse(DrawObject, XYObjectMixin):
         
         DrawObject.__init__(self,InForeground)
 
-        self.XY = array( (x, y), Float)
-        self.WH = array( (width, height), Float )
-        self.BoundingBox = array(((x,y), (self.XY + self.WH)), Float)
+        self.XY = array( XY, Float)
+        self.XY.shape = (2,)
+        self.WH = array( WH, Float )
+        self.WH.shape = (2,)
+        self.BoundingBox = array((self.XY, (self.XY + self.WH)), Float)
         self.LineColor = LineColor
         self.LineStyle = LineStyle
         self.LineWidth = LineWidth
@@ -720,6 +962,11 @@ class RectEllipse(DrawObject, XYObjectMixin):
         self.SetPen(LineColor,LineStyle,LineWidth)
         self.SetBrush(FillColor,FillStyle)
 
+    def SetShape(self, XY, WH):
+        self.XY = array( XY, Float)
+        self.WH = array( WH, Float )
+        self.CalcBoundingBox()
+
 
     def SetUpDraw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc):
         dc.SetPen(self.Pen)
@@ -730,17 +977,12 @@ class RectEllipse(DrawObject, XYObjectMixin):
         return ( WorldToPixel(self.XY),
                  ScaleWorldToPixel(self.WH) )
 
-    def SetXY(self, x, y):
-        self.XY = array( (x, y), Float)
+    def CalcBoundingBox(self):
         self.BoundingBox = array((self.XY, (self.XY + self.WH) ), Float)
-        if self._Canvas:
-            self._Canvas.BoundingBoxDirty = True
+        self._Canvas.BoundingBoxDirty = True
 
 
 class Rectangle(RectEllipse):
-#    def __init__(*args, **kwargs):
-#        RectEllipse.__init__(*args, **kwargs)
-#        raise "an error"
 
     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
         ( XY, WH ) = self.SetUpDraw(dc,
@@ -752,8 +994,6 @@ class Rectangle(RectEllipse):
             HTdc.DrawRectanglePointSize(XY, WH)
 
 class Ellipse(RectEllipse):
-#    def __init__(*args, **kwargs):
-#        RectEllipse.__init__(*args, **kwargs)
 
     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
         ( XY, WH ) = self.SetUpDraw(dc,
@@ -765,15 +1005,23 @@ class Ellipse(RectEllipse):
             HTdc.DrawEllipsePointSize(XY, WH)
 
 class Circle(Ellipse):
-    def __init__(self, x ,y, Diameter, **kwargs):
+
+    def __init__(self, XY, Diameter, **kwargs):
+        self.Center = array(XY, Float)
+        Diameter = float(Diameter)
         RectEllipse.__init__(self ,
-                             x-Diameter/2.,
-                             y-Diameter/2.,
-                             Diameter,
-                             Diameter,
+                             self.Center - Diameter/2.0,
+                             (Diameter, Diameter),
                              **kwargs)
+
+    def SetDiameter(self, Diameter):
+        Diameter = float(Diameter)
+        XY = self.Center - (Diameter/2.0)
+        self.SetShape(XY,
+                      (Diameter, Diameter)
+                      )
         
-class TextObjectMixin:
+class TextObjectMixin(XYObjectMixin):
     """
 
     A mix in class that holds attributes and methods that are needed by
@@ -787,6 +1035,8 @@ class TextObjectMixin:
 
     FontList = {}
 
+    LayoutFontSize = 12 # font size used for calculating layout
+
     def SetFont(self, Size, Family, Style, Weight, Underline, FaceName):
         self.Font = self.FontList.setdefault( (Size,
                                                Family,
@@ -802,18 +1052,48 @@ class TextObjectMixin:
                                                        FaceName) )
         return self.Font
 
+    def SetColor(self, Color):
+        self.Color = Color
+
+    def SetBackgroundColor(self, BackgroundColor):
+        self.BackgroundColor = BackgroundColor
+
+    def SetText(self, String):
+        """
+        Re-sets the text displayed by the object
+
+        In the case of the ScaledTextBox, it will re-do the layout as appropriate
+
+        Note: only tested with the ScaledTextBox
+
+        """
+
+        self.String = String
+        self.LayoutText()
+
+    def LayoutText(self):
+        """
+        A dummy method to re-do the layout of the text.
+
+        A derived object needs to override this if required.
+
+        """
+        pass
+
     ## store the function that shift the coords for drawing text. The
     ## "c" parameter is the correction for world coordinates, rather
     ## than pixel coords as the y axis is reversed
-    ShiftFunDict = {'tl': lambda x, y, w, h, world=0: (x, y) ,
-                    'tc': lambda x, y, w, h, world=0: (x - w/2, y) , 
-                    'tr': lambda x, y, w, h, world=0: (x - w, y) , 
-                    'cl': lambda x, y, w, h, world=0: (x, y - h/2 + world*h) , 
-                    'cc': lambda x, y, w, h, world=0: (x - w/2, y - h/2 + world*h) , 
-                    'cr': lambda x, y, w, h, world=0: (x - w, y - h/2 + world*h) ,
-                    'bl': lambda x, y, w, h, world=0: (x, y - h + 2*world*h) ,
-                    'bc': lambda x, y, w, h, world=0: (x - w/2, y - h + 2*world*h) , 
-                    'br': lambda x, y, w, h, world=0: (x - w, y - h + 2*world*h)}
+    ## pad is the extra space around the text
+    ## if world = 1, the vertical shift is done in y-up coordinates
+    ShiftFunDict = {'tl': lambda x, y, w, h, world=0, pad=0: (x + pad,     y + pad - 2*world*pad),
+                    'tc': lambda x, y, w, h, world=0, pad=0: (x - w/2,     y + pad - 2*world*pad), 
+                    'tr': lambda x, y, w, h, world=0, pad=0: (x - w - pad, y + pad - 2*world*pad), 
+                    'cl': lambda x, y, w, h, world=0, pad=0: (x + pad,     y - h/2 + world*h), 
+                    'cc': lambda x, y, w, h, world=0, pad=0: (x - w/2,     y - h/2 + world*h), 
+                    'cr': lambda x, y, w, h, world=0, pad=0: (x - w - pad, y - h/2 + world*h),
+                    'bl': lambda x, y, w, h, world=0, pad=0: (x + pad,     y - h + 2*world*h - pad + world*2*pad) ,
+                    'bc': lambda x, y, w, h, world=0, pad=0: (x - w/2,     y - h + 2*world*h - pad + world*2*pad) , 
+                    'br': lambda x, y, w, h, world=0, pad=0: (x - w - pad, y - h + 2*world*h - pad + world*2*pad)}
 
 class Text(DrawObject, TextObjectMixin):
     """
@@ -848,7 +1128,8 @@ class Text(DrawObject, TextObjectMixin):
         The value can be True or False. At present this may have an an
         effect on Windows only.
 
-    Alternatively, you can set the kw arg: Font, to a wx.Font, and the above will be ignored.
+    Alternatively, you can set the kw arg: Font, to a wx.Font, and the
+    above will be ignored.
     
     The size is fixed, and does not scale with the drawing.
 
@@ -856,7 +1137,7 @@ class Text(DrawObject, TextObjectMixin):
 
     """
     
-    def __init__(self,String,x,y,
+    def __init__(self,String, xy,
                  Size =  12,
                  Color = "Black",
                  BackgroundColor = None,
@@ -889,25 +1170,14 @@ class Text(DrawObject, TextObjectMixin):
             Weight             =  Font.GetWeight()
         self.SetFont(Size, Family, Style, Weight, Underline, FaceName)
 
-        self.BoundingBox = array(((x,y),(x,y)),Float)
+        self.BoundingBox = array((xy, xy),Float)
 
-        self.XY = ( x,y )
+        self.XY = asarray(xy)
+        self.XY.shape = (2,)
 
-       # use a memDC --  ScreenDC doesn't work with 2.5.1 and GTK2
-        #dc = wx.MemoryDC()
-        #bitmap = wx.EmptyBitmap(1, 1)
-        #dc.SelectObject(bitmap)               
-        #dc.SetFont(self.Font)
-        #(self.TextWidth, self.TextHeight) = dc.GetTextExtent(self.String)
         (self.TextWidth, self.TextHeight) = (None, None)
         self.ShiftFun = self.ShiftFunDict[Position]
 
-    def SetXY(self, x, y):
-        self.XY = ( x,y )
-        self.BoundingBox = array((self.XY, self.XY),Float)
-        if self._Canvas:
-            self._Canvas.BoundingBoxDirty = True
-
     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
         XY = WorldToPixel(self.XY)
         dc.SetFont(self.Font)
@@ -926,7 +1196,7 @@ class Text(DrawObject, TextObjectMixin):
             HTdc.SetBrush(self.HitBrush)
             HTdc.DrawRectanglePointSize(XY, (self.TextWidth, self.TextHeight) )
 
-class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
+class ScaledText(DrawObject, TextObjectMixin):
     """
     This class creates a text object that is scaled when zoomed.  It is
     placed at the coordinates, x,y. the "Position" argument is a two
@@ -942,29 +1212,25 @@ class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
 
     Family:
         Font family, a generic way of referring to fonts without
-        specifying actual facename. One of::
+        specifying actual facename. One of:
             wx.DEFAULT:  Chooses a default font. 
             wx.DECORATI: A decorative font. 
             wx.ROMAN: A formal, serif font. 
             wx.SCRIPT: A handwriting font. 
             wx.SWISS: A sans-serif font. 
             wx.MODERN: A fixed pitch font.
-            
         NOTE: these are only as good as the wxWindows defaults, which aren't so good.
-        
     Style:
         One of wx.NORMAL, wx.SLANT and wx.ITALIC.
-        
     Weight:
         One of wx.NORMAL, wx.LIGHT and wx.BOLD.
-        
     Underline:
         The value can be True or False. At present this may have an an
         effect on Windows only.
 
     Alternatively, you can set the kw arg: Font, to a wx.Font, and the
     above will be ignored. The size of the font you specify will be
-    ignored, but the rest of it's attributes will be preserved.
+    ignored, but the rest of its attributes will be preserved.
     
     The size will scale as the drawing is zoomed.
 
@@ -983,11 +1249,11 @@ class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
     zoom limit at that point.
 
     The hit-test is done on the entire text extent. This could be made
-    optional, but I havn't gotten around to it.
+    optional, but I haven't gotten around to it.
 
     """
     
-    def __init__(self, String, x, y , Size,
+    def __init__(self, String, XY , Size,
                  Color = "Black",
                  BackgroundColor = None,
                  Family = wx.MODERN,
@@ -1001,7 +1267,8 @@ class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
         DrawObject.__init__(self,InForeground)
 
         self.String = String
-        self.XY = array( (x, y), Float)
+        self.XY = array( XY, Float)
+        self.XY.shape = (2,)
         self.Size = Size     
         self.Color = Color
         self.BackgroundColor = BackgroundColor
@@ -1019,31 +1286,40 @@ class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
             self.Weight             =  Font.GetWeight()    
 
         # Experimental max font size value on wxGTK2: this works OK on
-        # my system If it's any larger, there is a crash, with the
-        # message: The application 'FloatCanvasDemo.py' lost its
+        # my system. If it's a lot  larger, there is a crash, with the
+        # message:
+        #
+        # The application 'FloatCanvasDemo.py' lost its
         # connection to the display :0.0; most likely the X server was
         # shut down or you killed/destroyed the application.
-        self.MaxSize = 2750
+        #
+        # Windows and OS-X seem to be better behaved in this regard.
+        # They may not draw it, but they don't crash either!
+        self.MaxFontSize = 1000
         
         self.ShiftFun = self.ShiftFunDict[Position]
 
-        ## Compute the BB
+        self.CalcBoundingBox()
+
+    def LayoutText(self):
+        # This will be called when the text is re-set
+        # nothing much to be done here
+        self.CalcBoundingBox()
+
+    def CalcBoundingBox(self):
         ## this isn't exact, as fonts don't scale exactly.
         dc = wx.MemoryDC()
         bitmap = wx.EmptyBitmap(1, 1)
         dc.SelectObject(bitmap) #wxMac needs a Bitmap selected for GetTextExtent to work.
         DrawingSize = 40 # pts This effectively determines the resolution that the BB is computed to.
-        ScaleFactor = float(Size) / DrawingSize
+        ScaleFactor = float(self.Size) / DrawingSize
         dc.SetFont(self.SetFont(DrawingSize, self.Family, self.Style, self.Weight, self.Underline, self.FaceName) )
         (w,h) = dc.GetTextExtent(self.String)
         w = w * ScaleFactor
         h = h * ScaleFactor
-        x, y = self.ShiftFun(x, y, w, h, world = 1)
+        x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world = 1)
         self.BoundingBox = array(((x, y-h ),(x + w, y)),Float)
-
-        # the new coords are set to the corner of the BB:
-        #self.X = self.BoundingBox[0,0]
-        #self.Y = self.BoundingBox[1,1]
+        
     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
         (X,Y) = WorldToPixel( (self.XY) )
 
@@ -1052,8 +1328,7 @@ class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
         ## Check to see if the font size is large enough to blow up the X font server
         ## If so, limit it. Would it be better just to not draw it?
         ## note that this limit is dependent on how much memory you have, etc.
-        if Size > self.MaxSize:
-            Size = self.MaxSize
+        Size = min(Size, self.MaxFontSize)
         dc.SetFont(self.SetFont(Size, self.Family, self.Style, self.Weight, self.Underline, self.FaceName))
         dc.SetTextForeground(self.Color)
         if self.BackgroundColor:
@@ -1073,9 +1348,420 @@ class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
             HTdc.SetBrush(self.HitBrush)
             HTdc.DrawRectanglePointSize(xy, (w, h) )
 
+class ScaledTextBox(DrawObject, TextObjectMixin):
+    """
+    This class creates a TextBox object that is scaled when zoomed.  It is
+    placed at the coordinates, x,y.
+
+    If the Width parameter is defined, the text will be wrapped to the width given.
+
+    A Box can be drawn around the text, be specifying:
+    LineWidth and/or  FillColor 
+
+    A space(margin) can be put all the way around the text, be specifying:
+    the PadSize argument in world coordinates.
+
+    The spacing between lines can be adjusted with the:
+    LineSpacing argument.
+
+    The "Position" argument is a two character string, indicating where
+    in relation to the coordinates the Box should be oriented.
+    -The first letter is: t, c, or b, for top, center and bottom.
+    -The second letter is: l, c, or r, for left, center and right The
+    position refers to the position relative to the text itself. It
+    defaults to "tl" (top left).
+
+    Size is the size of the font in world coordinates.
+
+    Family:
+        Font family, a generic way of referring to fonts without
+        specifying actual facename. One of:
+            wx.DEFAULT:  Chooses a default font. 
+            wx.DECORATIVE: A decorative font. 
+            wx.ROMAN: A formal, serif font. 
+            wx.SCRIPT: A handwriting font. 
+            wx.SWISS: A sans-serif font. 
+            wx.MODERN: A fixed pitch font.
+        NOTE: these are only as good as the wxWindows defaults, which aren't so good.
+    Style:
+        One of wx.NORMAL, wx.SLANT and wx.ITALIC.
+    Weight:
+        One of wx.NORMAL, wx.LIGHT and wx.BOLD.
+    Underline:
+        The value can be True or False. At present this may have an an
+        effect on Windows only.
+
+    Alternatively, you can set the kw arg: Font, to a wx.Font, and the
+    above will be ignored. The size of the font you specify will be
+    ignored, but the rest of its attributes will be preserved.
+    
+    The size will scale as the drawing is zoomed.
+
+    Bugs/Limitations:
+
+    As fonts are scaled, they do end up a little different, so you don't
+    get exactly the same picture as you scale up and down, but it's
+    pretty darn close.
+    
+    On wxGTK1 on my Linux system, at least, using a font of over about
+    1000 pts. brings the system to a halt. It's the Font Server using
+    huge amounts of memory. My work around is to max the font size to
+    1000 points, so it won't scale past there. GTK2 uses smarter font
+    drawing, so that may not be an issue in future versions, so feel
+    free to test. Another smarter way to do it would be to set a global
+    zoom limit at that point.
+
+    The hit-test is done on the entire box. This could be made
+    optional, but I haven't gotten around to it.
+
+    """
+        
+    def __init__(self, String,
+                 Point,
+                 Size,
+                 Color = "Black",
+                 BackgroundColor = None,
+                 LineColor = 'Black',
+                 LineStyle = 'Solid',
+                 LineWidth = 1,
+                 Width = None,
+                 PadSize = None,
+                 Family = wx.MODERN,
+                 Style = wx.NORMAL,
+                 Weight = wx.NORMAL,
+                 Underline = False,
+                 Position = 'tl',
+                 Alignment = "left",
+                 Font = None,
+                 LineSpacing = 1.0,
+                 InForeground = False):
+        
+        DrawObject.__init__(self,InForeground)
+
+        self.XY = array(Point, Float)
+        self.Size = Size     
+        self.Color = Color
+        self.BackgroundColor = BackgroundColor
+        self.LineColor = LineColor
+        self.LineStyle = LineStyle
+        self.LineWidth = LineWidth
+        self.Width = Width
+        if PadSize is None: # the default is just a little bit of padding
+            self.PadSize = Size/10.0
+        else:
+            self.PadSize = float(PadSize)
+        self.Family = Family   
+        self.Style = Style    
+        self.Weight = Weight   
+        self.Underline = Underline
+        self.Alignment = Alignment.lower()
+        self.LineSpacing = float(LineSpacing)
+        self.Position = Position
+        
+        if not Font:
+            self.FaceName = ''
+        else:
+            self.FaceName           =  Font.GetFaceName()           
+            self.Family             =  Font.GetFamily()    
+            self.Style              =  Font.GetStyle()     
+            self.Underlined         =  Font.GetUnderlined()         
+            self.Weight             =  Font.GetWeight()    
+
+        # Experimental max font size value on wxGTK2: this works OK on
+        # my system. If it's a lot  larger, there is a crash, with the
+        # message:
+        #
+        # The application 'FloatCanvasDemo.py' lost its
+        # connection to the display :0.0; most likely the X server was
+        # shut down or you killed/destroyed the application.
+        #
+        # Windows and OS-X seem to be better behaved in this regard.
+        # They may not draw it, but they don't crash either!
+
+        self.MaxFontSize = 1000
+        self.ShiftFun = self.ShiftFunDict[Position]
+
+        self.String = String
+        self.LayoutText()
+        self.CalcBoundingBox()
+
+        self.SetPen(LineColor,LineStyle,LineWidth)
+        self.SetBrush(BackgroundColor, "Solid")
+
+
+    def WrapToWidth(self):
+        dc = wx.MemoryDC()
+        bitmap = wx.EmptyBitmap(1, 1)
+        dc.SelectObject(bitmap) #wxMac needs a Bitmap selected for GetTextExtent to work.
+        DrawingSize = self.LayoutFontSize # pts This effectively determines the resolution that the BB is computed to.
+        ScaleFactor = float(self.Size) / DrawingSize
+        Width = (self.Width - 2*self.PadSize) / ScaleFactor #Width to wrap to
+        dc.SetFont(self.SetFont(DrawingSize, self.Family, self.Style, self.Weight, self.Underline, self.FaceName) )
+
+        NewStrings = []
+        for s in self.Strings:
+            #beginning = True
+            text = s.split(" ")
+            text.reverse()
+            LineLength = 0
+            NewText = text[-1]
+            del text[-1]
+            while text:
+                w  = dc.GetTextExtent(' ' + text[-1])[0]
+                if LineLength + w <= Width:
+                    NewText += ' '
+                    NewText += text[-1]
+                    LineLength = dc.GetTextExtent(NewText)[0]
+                else:
+                    NewStrings.append(NewText)
+                    NewText = text[-1]
+                    LineLength = dc.GetTextExtent(text[-1])[0]
+                del text[-1]
+            NewStrings.append(NewText)
+        self.Strings = NewStrings
+
+    def ReWrap(self, Width):
+        self.Width = Width
+        self.LayoutText()
+
+    def LayoutText(self):
+        """
+
+        Calculates the positions of the words of text.
+
+        This isn't exact, as fonts don't scale exactly.
+        To help this, the position of each individual word
+        is stored separately, so that the general layout stays
+        the same in world coordinates, as the fonts scale.
+
+        """
+        self.Strings = self.String.split("\n")
+        if self.Width:
+            self.WrapToWidth()
+
+        dc = wx.MemoryDC()
+        bitmap = wx.EmptyBitmap(1, 1)
+        dc.SelectObject(bitmap) #wxMac needs a Bitmap selected for GetTextExtent to work.
+
+        DrawingSize = self.LayoutFontSize # pts This effectively determines the resolution that the BB is computed to.
+        ScaleFactor = float(self.Size) / DrawingSize
+
+        dc.SetFont(self.SetFont(DrawingSize, self.Family, self.Style, self.Weight, self.Underline, self.FaceName) )
+
+        TextHeight = dc.GetTextExtent("X")[1]
+        SpaceWidth = dc.GetTextExtent(" ")[0]
+        LineHeight = TextHeight * self.LineSpacing
+
+        LineWidths = zeros((len(self.Strings),), Float)
+        y = 0
+        Words = []
+        AllLinePoints = []
+
+        for i, s in enumerate(self.Strings):
+            LineWidths[i] = 0
+            LineWords = s.split(" ")
+            LinePoints = zeros((len(LineWords),2), Float)
+            for j, word in enumerate(LineWords):
+                if j > 0:
+                    LineWidths[i] += SpaceWidth
+                Words.append(word)
+                LinePoints[j] = (LineWidths[i], y)
+                w = dc.GetTextExtent(word)[0]
+                LineWidths[i] += w
+            y -= LineHeight
+            AllLinePoints.append(LinePoints)
+        TextWidth = maximum.reduce(LineWidths)
+        self.Words = Words
+
+        if self.Width is None:
+            BoxWidth = TextWidth * ScaleFactor + 2*self.PadSize
+        else: # use the defined Width
+            BoxWidth = self.Width
+        Points = zeros((0,2), Float)
+
+        for i, LinePoints in enumerate(AllLinePoints):
+            ## Scale to World Coords.
+            LinePoints *= (ScaleFactor, ScaleFactor)
+            if self.Alignment == 'left':
+                LinePoints[:,0] += self.PadSize
+            elif self.Alignment == 'center':
+                LinePoints[:,0] += (BoxWidth - LineWidths[i]*ScaleFactor)/2.0
+            elif self.Alignment == 'right':
+                LinePoints[:,0] += (BoxWidth - LineWidths[i]*ScaleFactor-self.PadSize)
+            Points = concatenate((Points, LinePoints))
+
+        BoxHeight = -(Points[-1,1] - (TextHeight * ScaleFactor)) + 2*self.PadSize
+        (x,y) = self.ShiftFun(self.XY[0], self.XY[1], BoxWidth, BoxHeight, world=1)
+        Points += (0, -self.PadSize)
+        self.Points = Points
+        self.BoxWidth = BoxWidth
+        self.BoxHeight = BoxHeight
+        self.CalcBoundingBox()
+
+    def CalcBoundingBox(self):
+
+        """
+        
+        Calculates the Bounding Box
+
+        """
+
+        w, h = self.BoxWidth, self.BoxHeight
+        x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world=1)
+        self.BoundingBox = array(((x, y-h ),(x + w, y)),Float)
+
+    def GetBoxRect(self):
+        wh = (self.BoxWidth, self.BoxHeight)
+        xy = (self.BoundingBox[0,0], self.BoundingBox[1,1])
+
+        return (xy, wh)
+
+    def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+        xy, wh = self.GetBoxRect()
+        
+        Points = self.Points + xy 
+        Points = WorldToPixel(Points) 
+        xy = WorldToPixel(xy)
+        wh = ScaleWorldToPixel(wh) * (1,-1)
+
+        # compute the font size:
+        Size = abs( ScaleWorldToPixel( (self.Size, self.Size) )[1] ) # only need a y coordinate length
+        ## Check to see if the font size is large enough to blow up the X font server
+        ## If so, limit it. Would it be better just to not draw it?
+        ## note that this limit is dependent on how much memory you have, etc.
+        Size = min(Size, self.MaxFontSize)
+        
+        font = self.SetFont(Size, self.Family, self.Style, self.Weight, self.Underline, self.FaceName)
+        dc.SetFont(font)
+        dc.SetTextForeground(self.Color)
+        dc.SetBackgroundMode(wx.TRANSPARENT)
+
+        # Draw The Box
+        if (self.LineStyle and self.LineColor) or self.BackgroundColor:
+            dc.SetBrush(self.Brush)
+            dc.SetPen(self.Pen)
+            dc.DrawRectanglePointSize(xy , wh)
+            
+        # Draw the Text
+        dc.DrawTextList(self.Words, Points)
+
+        # Draw the hit box.
+        if HTdc and self.HitAble:
+            HTdc.SetPen(self.HitPen)
+            HTdc.SetBrush(self.HitBrush)
+            HTdc.DrawRectanglePointSize(xy, wh)
+
+class Bitmap(DrawObject, TextObjectMixin):
+    """
+    This class creates a bitmap object, placed at the coordinates,
+    x,y. the "Position" argument is a two charactor string, indicating
+    where in relation to the coordinates the bitmap should be oriented.
+
+    The first letter is: t, c, or b, for top, center and bottom The
+    second letter is: l, c, or r, for left, center and right The
+    position refers to the position relative to the text itself. It
+    defaults to "tl" (top left).
+
+    The size is fixed, and does not scale with the drawing.
+
+    """
+    
+    def __init__(self,Bitmap,XY,
+                 Position = 'tl',
+                 InForeground = False):
+        
+        DrawObject.__init__(self,InForeground)
+
+        if type(Bitmap) == wx._gdi.Bitmap:
+            self.Bitmap = Bitmap
+        elif type(Bitmap) == wx._core.Image:
+            self.Bitmap = wx.BitmapFromImage(Bitmap)
+        
+        # Note the BB is just the point, as the size in World coordinates is not fixed
+        self.BoundingBox = array((XY,XY),Float)
+
+        self.XY = XY
+
+        (self.Width, self.Height) = self.Bitmap.GetWidth(), self.Bitmap.GetHeight()
+        self.ShiftFun = self.ShiftFunDict[Position]
+
+    def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+        XY = WorldToPixel(self.XY)
+        XY = self.ShiftFun(XY[0], XY[1], self.Width, self.Height)
+        dc.DrawBitmapPoint(self.Bitmap, XY, True)
+        if HTdc and self.HitAble:
+            HTdc.SetPen(self.HitPen)
+            HTdc.SetBrush(self.HitBrush)
+            HTdc.DrawRectanglePointSize(XY, (self.Width, self.Height) )
+
+class ScaledBitmap(DrawObject, TextObjectMixin):
+    """
+    
+    This class creates a bitmap object, placed at the coordinates, XY,
+    of Height, H, in World coorsinates. The width is calculated from the
+    aspect ratio of the bitmap.
+    
+    the "Position" argument is a two charactor string, indicating
+    where in relation to the coordinates the bitmap should be oriented.
+
+    The first letter is: t, c, or b, for top, center and bottom The
+    second letter is: l, c, or r, for left, center and right The
+    position refers to the position relative to the text itself. It
+    defaults to "tl" (top left).
+
+    The size scales with the drawing
+
+    """
+    
+    def __init__(self,
+                 Bitmap,
+                 XY,
+                 Height,
+                 Position = 'tl',
+                 InForeground = False):
+        
+        DrawObject.__init__(self,InForeground)
+
+        if type(Bitmap) == wx._gdi.Bitmap:
+            self.Image = Bitmap.ConvertToImage()
+        elif type(Bitmap) == wx._core.Image:
+            self.Image = Bitmap
+            
+        self.XY = XY
+        self.Height = Height 
+        (self.bmpWidth, self.bmpHeight) = self.Image.GetWidth(), self.Image.GetHeight()
+        self.Width = self.bmpWidth / self.bmpHeight * Height
+        self.ShiftFun = self.ShiftFunDict[Position]
+        self.CalcBoundingBox()
+        self.ScaledBitmap = None
+        self.ScaledHeight = None
+
+    def CalcBoundingBox(self):
+        ## this isn't exact, as fonts don't scale exactly.
+        w,h = self.Width, self.Height
+        x, y = self.ShiftFun(self.XY[0], self.XY[1], w, h, world = 1)
+        self.BoundingBox = array(((x, y-h ),(x + w, y)),Float)
+
+    def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
+        XY = WorldToPixel(self.XY)
+        H = ScaleWorldToPixel(self.Height)[0]
+        W = H * (self.bmpWidth / self.bmpHeight)
+        if (self.ScaledBitmap is None) or (H <> self.ScaledHeight) :
+            self.ScaledHeight = H
+            Img = self.Image.Scale(W, H)
+            self.ScaledBitmap = wx.BitmapFromImage(Img)
+
+        XY = self.ShiftFun(XY[0], XY[1], W, H)
+        dc.DrawBitmapPoint(self.ScaledBitmap, XY, True)
+        if HTdc and self.HitAble:
+            HTdc.SetPen(self.HitPen)
+            HTdc.SetBrush(self.HitBrush)
+            HTdc.DrawRectanglePointSize(XY, (W, H) )
+
 
 #---------------------------------------------------------------------------
 class FloatCanvas(wx.Panel):
+    ## fixme: could this be a wx.Window?
     """
     FloatCanvas.py
 
@@ -1163,7 +1849,7 @@ class FloatCanvas(wx.Panel):
         
         global ScreenPPI ## A global variable to hold the Pixels per inch that wxWindows thinks is in use.
         dc = wx.ScreenDC()
-        ScreenPPI = dc.GetPPI()[0] # Assume square pixels
+        ScreenPPI = dc.GetPPI()[1] # Pixel height
         del dc
 
         self.HitColorGenerator = None
@@ -1196,7 +1882,7 @@ class FloatCanvas(wx.Panel):
 
         ## create the Hit Test Dicts:
         self.HitDict = None
-
+        self._HTdc = None
 
         self._DrawList = []
         self._ForeDrawList = []
@@ -1220,28 +1906,75 @@ class FloatCanvas(wx.Panel):
         self.ObjectUnderMouse = None
         
         # called just to make sure everything is initialized
-        self.OnSize(None)
-
+        # this is a bug on OS-X, maybe it's not required?
+        self.SizeTimer = wx.PyTimer(self.OnSizeTimer) # timer to give a delay when re-sizing so that bufferes aren't re-built too many times.
+        
+        self.InitializePanel()
+        self.MakeNewBuffers()
+        
         self.InHereNum = 0
 
+        self.CreateCursors()
+        
+    def CreateCursors(self):
+
+        ## create all the Cursors, so they don't need to be created each time.
+        ##
+        if "wxMac" in wx.PlatformInfo: # use 16X16 cursors for wxMac
+            self.HandCursor = wx.CursorFromImage(Resources.getHand16Image())
+            self.GrabHandCursor = wx.CursorFromImage(Resources.getGrabHand16Image())
+
+            img = Resources.getMagPlus16Image()
+            img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 6)
+            img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 6)
+            self.MagPlusCursor = wx.CursorFromImage(img)
+
+            img = Resources.getMagMinus16Image()
+            img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 6)
+            img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 6)
+            self.MagMinusCursor = wx.CursorFromImage(img)
+        else: # use 24X24 cursors for GTK and Windows
+            self.HandCursor = wx.CursorFromImage(Resources.getHandImage())
+            self.GrabHandCursor = wx.CursorFromImage(Resources.getGrabHandImage())
+
+            img = Resources.getMagPlusImage()
+            img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 9)
+            img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 9)
+            self.MagPlusCursor = wx.CursorFromImage(img)
+
+            img = Resources.getMagMinusImage()
+            img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 9)
+            img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 9)
+            self.MagMinusCursor = wx.CursorFromImage(img)
+
     def SetProjectionFun(self,ProjectionFun):
         if ProjectionFun == 'FlatEarth':
             self.ProjectionFun = self.FlatEarthProjection 
-        elif type(ProjectionFun) == types.FunctionType:
+        elif callable(ProjectionFun):
             self.ProjectionFun = ProjectionFun 
         elif ProjectionFun is None:
             self.ProjectionFun = lambda x=None: array( (1,1), Float)
         else:
-            raise FloatCanvasException('Projectionfun must be either: "FlatEarth", None, or a function that takes the ViewPortCenter and returns a MapProjectionVector')
+            raise FloatCanvasError('Projectionfun must be either: "FlatEarth", None, or a callable object (function, for instance) that takes the ViewPortCenter and returns a MapProjectionVector')
 
-    def FlatEarthProjection(self,CenterPoint):
+    def FlatEarthProjection(self, CenterPoint):
         return array((cos(pi*CenterPoint[1]/180),1),Float)
 
     def SetMode(self,Mode):
-        if Mode in ["ZoomIn","ZoomOut","Move","Mouse",None]:
+        if Mode in ["ZoomIn","ZoomOut","Move","Mouse", None]:
+            if Mode == "Move":
+                self.SetCursor(self.HandCursor)
+            elif Mode == "ZoomIn":
+                self.SetCursor(self.MagPlusCursor) 
+            elif Mode == "ZoomOut":
+                self.SetCursor(self.MagMinusCursor) 
+            else:
+                self.SetCursor(wx.NullCursor)
+                
             self.GUIMode = Mode
+            
         else:
-            raise FloatCanvasException('"%s" is Not a valid Mode'%Mode)
+            raise FloatCanvasError('"%s" is Not a valid Mode'%Mode)
 
     def MakeHitDict(self):
         ##fixme: Should this just be None if nothing has been bound? 
@@ -1258,13 +1991,13 @@ class FloatCanvas(wx.Panel):
                         EVT_FC_LEAVE_OBJECT: {},
                         }        
             
-    def RaiseMouseEvent(self, Event, EventType):
+    def _RaiseMouseEvent(self, Event, EventType):
         """
         This is called in various other places to raise a Mouse Event
         """
         #print "in Raise Mouse Event", Event
         pt = self.PixelToWorld( Event.GetPosition() )
-        evt = MouseEvent(EventType, Event, self.GetId(), pt)
+        evt = _MouseEvent(EventType, Event, self.GetId(), pt)
         self.GetEventHandler().ProcessEvent(evt)       
 
     def HitTest(self, event, HitEvent):
@@ -1281,6 +2014,7 @@ class FloatCanvas(wx.Panel):
                     Object = self.HitDict[ HitEvent ][color]
                     ## Add the hit coords to the Object
                     Object.HitCoords = self.PixelToWorld( xy )
+                    Object.HitCoordsPixel = xy
                     Object.CallBackFuncs[HitEvent](Object)
                     return True
             return False
@@ -1342,42 +2076,42 @@ class FloatCanvas(wx.Panel):
         if self.GUIMode == "Mouse":
             EventType = EVT_FC_LEFT_DCLICK
             if not self.HitTest(event, EventType):
-                self.RaiseMouseEvent(event, EventType)
-
+                self._RaiseMouseEvent(event, EventType)
 
     def MiddleDownEvent(self,event):
         if self.GUIMode == "Mouse":
             EventType = EVT_FC_MIDDLE_DOWN
             if not self.HitTest(event, EventType):
-                self.RaiseMouseEvent(event, EventType)
+                self._RaiseMouseEvent(event, EventType)
 
     def MiddleUpEvent(self,event):
         if self.GUIMode == "Mouse":
             EventType = EVT_FC_MIDDLE_UP
             if not self.HitTest(event, EventType):
-                self.RaiseMouseEvent(event, EventType)
+                self._RaiseMouseEvent(event, EventType)
 
     def MiddleDoubleClickEvent(self,event):
         if self.GUIMode == "Mouse":
             EventType = EVT_FC_MIDDLE_DCLICK
             if not self.HitTest(event, EventType):
-                self.RaiseMouseEvent(event, EventType)
+                self._RaiseMouseEvent(event, EventType)
 
     def RightUpEvent(self,event):
         if self.GUIMode == "Mouse":
             EventType = EVT_FC_RIGHT_UP
             if not self.HitTest(event, EventType):
-                self.RaiseMouseEvent(event, EventType)
+                self._RaiseMouseEvent(event, EventType)
 
     def RightDoubleCLickEvent(self,event):
         if self.GUIMode == "Mouse":
             EventType = EVT_FC_RIGHT_DCLICK
             if not self.HitTest(event, EventType):
-                self.RaiseMouseEvent(event, EventType)
+                self._RaiseMouseEvent(event, EventType)
 
     def WheelEvent(self,event):
-        if self.GUIMode == "Mouse":
-            self.RaiseMouseEvent(event, EVT_FC_MOUSEWHEEL)
+        ##if self.GUIMode == "Mouse":
+        ## Why not always raise this?
+            self._RaiseMouseEvent(event, EVT_FC_MOUSEWHEEL)
 
 
     def LeftDownEvent(self,event):
@@ -1390,17 +2124,18 @@ class FloatCanvas(wx.Panel):
                 Center = self.PixelToWorld( event.GetPosition() )
                 self.Zoom(1/1.5,Center)
             elif self.GUIMode == "Move":
+                self.SetCursor(self.GrabHandCursor)
                 self.StartMove = array( event.GetPosition() )
                 self.PrevMoveXY = (0,0)
             elif self.GUIMode == "Mouse":
                 ## check for a hit
                 if not self.HitTest(event, EVT_FC_LEFT_DOWN):
-                   self.RaiseMouseEvent(event,EVT_FC_LEFT_DOWN)
+                   self._RaiseMouseEvent(event,EVT_FC_LEFT_DOWN)
         else: 
             pass
 
     def LeftUpEvent(self,event):
-       if self.HasCapture():
+        if self.HasCapture():
             self.ReleaseMouse()
         if self.GUIMode:
             if self.GUIMode == "ZoomIn":
@@ -1423,16 +2158,17 @@ class FloatCanvas(wx.Panel):
                         self.Zoom(1.5,Center)
                     self.StartRBBox = None
             elif self.GUIMode == "Move":
-                if not self.StartMove is None:
+                self.SetCursor(self.HandCursor)
+                if self.StartMove is not None:
                     StartMove = self.StartMove
                     EndMove = array((event.GetX(),event.GetY()))
                     if sum((StartMove-EndMove)**2) > 16:
-                        self.Move(StartMove-EndMove,'Pixel')
+                        self.MoveImage(StartMove-EndMove,'Pixel')
                     self.StartMove = None
             elif self.GUIMode == "Mouse":
                 EventType = EVT_FC_LEFT_UP
                 if not self.HitTest(event, EventType):
-                   self.RaiseMouseEvent(event, EventType)
+                   self._RaiseMouseEvent(event, EventType)
         else:
             pass
 
@@ -1466,6 +2202,7 @@ class FloatCanvas(wx.Panel):
                     x1,y1 = self.PrevMoveXY
                     x2,y2 = xy_tl
                     w,h = self.PanelSize
+                    ##fixme: This sure could be cleaner!
                     if x2 > x1 and y2 > y1:
                         xa = xb = x1
                         ya = yb = y1
@@ -1500,13 +2237,14 @@ class FloatCanvas(wx.Panel):
                         yb = y2 + h
                         wb = w
                         hb = y1 - y2
-                    
+               
                     dc.SetPen(wx.TRANSPARENT_PEN)
                     dc.SetBrush(self.BackgroundBrush)
                     dc.DrawRectangle(xa, ya, wa, ha)
                     dc.DrawRectangle(xb, yb, wb, hb)
                     self.PrevMoveXY = xy_tl
-                    if self._ForegroundBuffer:
+                    if self._ForeDrawList:
+                    ##if self._ForegroundBuffer:
                         dc.DrawBitmapPoint(self._ForegroundBuffer,xy_tl)
                     else:
                         dc.DrawBitmapPoint(self._Buffer,xy_tl)
@@ -1515,10 +2253,10 @@ class FloatCanvas(wx.Panel):
                 ## Only do something if there are mouse over events bound
                 if self.HitDict and (self.HitDict[ EVT_FC_ENTER_OBJECT ] or self.HitDict[ EVT_FC_LEAVE_OBJECT ] ):
                     if not self.MouseOverTest(event):
-                        self.RaiseMouseEvent(event,EVT_FC_MOTION)
+                        self._RaiseMouseEvent(event,EVT_FC_MOTION)
             else:
                     pass
-            self.RaiseMouseEvent(event,EVT_FC_MOTION)
+            self._RaiseMouseEvent(event,EVT_FC_MOTION)
         else:
             pass
 
@@ -1533,7 +2271,7 @@ class FloatCanvas(wx.Panel):
             elif self.GUIMode == "Mouse":
                 EventType = EVT_FC_RIGHT_DOWN
                 if not self.HitTest(event, EventType):
-                   self.RaiseMouseEvent(event, EventType)
+                   self._RaiseMouseEvent(event, EventType)
         else:
             pass
         
@@ -1573,15 +2311,25 @@ class FloatCanvas(wx.Panel):
         else:
            self._ForegroundHTdc = None 
     
-    def OnSize(self,event):
-        self.PanelSize  = array(self.GetClientSizeTuple(),Int32)
+    def OnSize(self, event=None):
+        self.InitializePanel()
+        self.SizeTimer.Start(50, oneShot=True)
+
+    def OnSizeTimer(self, event=None):
+        self.MakeNewBuffers()
+        self.Draw()
+
+    def InitializePanel(self):
+        self.PanelSize = self.GetClientSizeTuple()
+        if self.PanelSize == (0,0):
+            ## OS-X sometimes gives a Size event when the panel is size (0,0)
+            self.PanelSize = (2,2)
+        self.PanelSize  = array(self.PanelSize,  Int32)
         self.HalfPanelSize = self.PanelSize / 2 # lrk: added for speed in WorldToPixel
         if self.PanelSize[0] == 0 or self.PanelSize[1] == 0:
             self.AspectRatio = 1.0
         else:
             self.AspectRatio = float(self.PanelSize[0]) / self.PanelSize[1]
-        self.MakeNewBuffers()
-        self.Draw()
         
     def OnPaint(self, event):
         dc = wx.PaintDC(self)
@@ -1605,8 +2353,7 @@ class FloatCanvas(wx.Panel):
         animation, for instance.
         
         """
-        #print "In Draw"
-        if self.PanelSize < (1,1): 
+        if sometrue(self.PanelSize <= 2 ): # it's possible for this to get called before being properly initialized.
             return
         if self.Debug: start = clock()
         ScreenDC =  wx.ClientDC(self)
@@ -1690,7 +2437,7 @@ class FloatCanvas(wx.Panel):
 ##        else:
 ##            return False
 
-    def Move(self,shift,CoordType):
+    def MoveImage(self,shift,CoordType):
         """
         move the image in the window.
 
@@ -1709,7 +2456,8 @@ class FloatCanvas(wx.Panel):
 
         """
         
-        shift = array(shift,Float)
+        shift = asarray(shift,Float)
+        #print "shifting by:", shift
         if CoordType == 'Panel':# convert from panel coordinates
             shift = shift * array((-1,1),Float) *self.PanelSize/self.TransformVector
         elif CoordType == 'Pixel': # convert from pixel coordinates
@@ -1717,8 +2465,8 @@ class FloatCanvas(wx.Panel):
         elif CoordType == 'World': # No conversion
             pass
         else:
-            raise FloatCanvasException('CoordType must be either "Panel", "Pixel", or "World"')
-            
+            raise FloatCanvasError('CoordType must be either "Panel", "Pixel", or "World"')
+        
         self.ViewPortCenter = self.ViewPortCenter + shift 
         self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
         self.TransformVector = array((self.Scale,-self.Scale),Float) * self.MapProjectionVector
@@ -1797,6 +2545,9 @@ class FloatCanvas(wx.Panel):
         ##fixme: Using the list.remove method is kind of slow
         if Object.InForeground:
             self._ForeDrawList.remove(Object)
+            if not self._ForeDrawList:
+                self._ForegroundBuffer = None
+                self._ForegroundHTdc = None
         else:
             self._DrawList.remove(Object)
             self._BackgroundDirty = True
@@ -1847,7 +2598,7 @@ class FloatCanvas(wx.Panel):
             self.BoundingBox = None
             self.ViewPortCenter= array( (0,0), Float)
             self.TransformVector = array( (1,-1), Float)
-            self.MapProjectionVector = array( (1,1), Float)                    
+            self.MapProjectionVector = array( (1,1), Float)
             self.Scale = 1        
         self.BoundingBoxDirty = False
 
@@ -1868,7 +2619,9 @@ class FloatCanvas(wx.Panel):
         a 2-tuple, or sequence of 2-tuples.
         """
         #Note: this can be called by users code for various reasons, so asarray is needed.
-        return  (((asarray(Coordinates,Float) - self.ViewPortCenter)*self.TransformVector)+(self.HalfPanelSize)).astype('i')
+        return  (((asarray(Coordinates,Float) -
+                   self.ViewPortCenter)*self.TransformVector)+
+                 (self.HalfPanelSize)).astype('i')
 
     def ScaleWorldToPixel(self,Lengths):
         """
@@ -1918,33 +2671,32 @@ class FloatCanvas(wx.Panel):
         Blit = ScreenDC.Blit # for speed
         NumBetweenBlits = self.NumBetweenBlits # for speed
         for i, Object in enumerate(self._ShouldRedraw(DrawList, ViewPortBB)):
-            Object._Draw(dc, WorldToPixel, ScaleWorldToPixel, HTdc)
-            if i % NumBetweenBlits == 0:
-                Blit(0, 0, PanelSize0, PanelSize1, dc, 0, 0)
+            if Object.Visible:
+                Object._Draw(dc, WorldToPixel, ScaleWorldToPixel, HTdc)
+                if (i+1) % NumBetweenBlits == 0:
+                    Blit(0, 0, PanelSize0, PanelSize1, dc, 0, 0)
         dc.EndDrawing()
 
-##    ## This is a way to automatically add a AddObject method for each
-##    ## object type This code has been replaced by Leo's code above, so
-##    ## that it happens at module init, rather than as needed. The
-##    ## primary advantage of this is that dir(FloatCanvas) will have
-##    ## them, and docstrings are preserved. Probably more useful
-##    ## exceptions if there is a problem, as well.
-##    def __getattr__(self, name):
-##        if name[:3] == "Add":
-##            func=globals()[name[3:]]
-##            def AddFun(*args, **kwargs):
-##                Object = func(*args, **kwargs)
-##                self.AddObject(Object)
-##                return Object
-##            ## add it to FloatCanvas' dict for future calls.
-##            self.__dict__[name] = AddFun
-##            return AddFun
-##        else:
-##            raise AttributeError("FloatCanvas has no attribute '%s'"%name)
+    def SaveAsImage(self, filename, ImageType=wx.BITMAP_TYPE_PNG):
+        """
+        
+        Saves the current image as an image file. The default is in the
+        PNG format. Other formats can be spcified using the wx flags:
+
+        wx.BITMAP_TYPE_BMP
+        wx.BITMAP_TYPE_XBM
+        wx.BITMAP_TYPE_XPM
+        etc. (see the wx docs for the complete list)
+
+        """
+        
+        self._Buffer.SaveFile(filename, ImageType)
+
 
 def _makeFloatCanvasAddMethods(): ## lrk's code for doing this in module __init__
     classnames = ["Circle", "Ellipse", "Rectangle", "ScaledText", "Polygon",
-               "Line", "Text", "PointSet"]
+                  "Line", "Text", "PointSet","Point", "Arrow","ScaledTextBox",
+                  "SquarePoint","Bitmap", "ScaledBitmap"]
     for classname in classnames:
         klass = globals()[classname]
         def getaddshapemethod(klass=klass):
@@ -1956,8 +2708,8 @@ def _makeFloatCanvasAddMethods(): ## lrk's code for doing this in module __init_
         addshapemethod = getaddshapemethod()
         methodname = "Add" + classname
         setattr(FloatCanvas, methodname, addshapemethod)
-        docstring = "        Creates %s and adds its reference to the canvas.\n" % classname
-        docstring += "        Argument protocol same as %s class" % classname
+        docstring = "Creates %s and adds its reference to the canvas.\n" % classname
+        docstring += "Argument protocol same as %s class" % classname
         if klass.__doc__:
             docstring += ", whose docstring is:\n%s" % klass.__doc__
         FloatCanvas.__dict__[methodname].__doc__ = docstring