]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/floatcanvas/FloatCanvas.py
more informative assert message
[wxWidgets.git] / wxPython / wx / lib / floatcanvas / FloatCanvas.py
index 83865fbc3475030f3d748d89cb59ba4da8614d99..5a9b74186c2fb63b1cda56b3a5334ddf125a59da 100644 (file)
@@ -1,29 +1,34 @@
+from __future__ import division
 
 try:
 
 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:
 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
 
 
 from time import clock, sleep
 
+import Resources # A file with icons, etc for FloatCanvas
+
 import wx
 
 import types
 import os        
 
 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.
 ## 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:
 
 
 ## a custom Exceptions:
 
-class FloatCanvasException(Exception):
+class FloatCanvasError(Exception):
     pass
 
     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() 
 #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()
 
 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 ):
 #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 )
 
 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.
 
 
     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.
 
     """
     the window hierarchy until it is handled.
 
     """
@@ -94,8 +100,9 @@ class MouseEvent(wx.PyCommandEvent):
         self._NativeEvent = NativeEvent
         self.Coords = Coords
     
         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
         
     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)
 
         #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):
     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
 
                 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
     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
         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:
 ##    """
 
 ##class ObjectSetMixin:
 ##    """
@@ -246,15 +207,16 @@ def colorGenerator():
 ##        if length == 1:
 ##            self.Pens = self.Pens[0]
 
 ##        if length == 1:
 ##            self.Pens = self.Pens[0]
 
-
-
 class DrawObject:
     """
     This is the base class for all the objects that can be drawn.
 
 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
         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.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
     # 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.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)
                 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
 
             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:
     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
 
                     pass
         self.HitAble = False
 
+
     def SetBrush(self,FillColor,FillStyle):
         if FillColor is None or FillStyle is None:
             self.Brush = wx.TRANSPARENT_BRUSH
     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] ) )
             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:
         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:
 
     def PutInBackground(self):
         if self._Canvas and self.InForeground:
@@ -387,6 +357,61 @@ class DrawObject:
             self._Canvas._BackgroundDirty = True
             self.InForeground = True
 
             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:
     """
 
 class XYObjectMixin:
     """
 
@@ -406,9 +431,26 @@ class XYObjectMixin:
         Delta = asarray(Delta, Float)
         self.XY += Delta
         self.BoundingBox = self.BoundingBox + Delta
         Delta = asarray(Delta, Float)
         self.XY += Delta
         self.BoundingBox = self.BoundingBox + Delta
+        
         if self._Canvas:
             self._Canvas.BoundingBoxDirty = True      
 
         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:
     """
 
 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,
 ##    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      
 
 ##        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
 
         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.
 
     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,
     """
     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
                  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
 
         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):
         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)
         dc.SetPen(self.Pen)
         dc.SetBrush(self.Brush)
         dc.DrawPolygon(Points)
@@ -517,13 +588,14 @@ class Polygon(DrawObject,PointsObjectMixin):
 ##        dc.DrawLineList(Points,self.Pens)
  
 
 ##        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,
 
     """
     def __init__(self,Points,
@@ -535,7 +607,7 @@ class Line(DrawObject,PointsObjectMixin):
 
 
         self.Points = array(Points,Float)
 
 
         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
 
         self.LineColor = LineColor
         self.LineStyle = LineStyle
@@ -554,6 +626,99 @@ class Line(DrawObject,PointsObjectMixin):
             HTdc.SetPen(self.HitPen)
             HTdc.DrawLines(Points)
 
             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.
 ##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)
 
 ##        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.
+
+    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.
 
 
-    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.
+    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.
 
 
     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.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.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
 
     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))
         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)
         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)
             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)
             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,
                  LineColor = "Black",
                  LineStyle = "Solid",
                  LineWidth    = 1,
@@ -706,9 +946,11 @@ class RectEllipse(DrawObject, XYObjectMixin):
         
         DrawObject.__init__(self,InForeground)
 
         
         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
         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)
 
         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)
 
     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) )
 
         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)
         self.BoundingBox = array((self.XY, (self.XY + self.WH) ), Float)
-        if self._Canvas:
-            self._Canvas.BoundingBoxDirty = True
+        self._Canvas.BoundingBoxDirty = True
 
 
 class Rectangle(RectEllipse):
 
 
 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,
 
     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):
             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,
 
     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):
             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 ,
         RectEllipse.__init__(self ,
-                             x-Diameter/2.,
-                             y-Diameter/2.,
-                             Diameter,
-                             Diameter,
+                             self.Center - Diameter/2.0,
+                             (Diameter, Diameter),
                              **kwargs)
                              **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
     """
 
     A mix in class that holds attributes and methods that are needed by
@@ -787,6 +1035,8 @@ class TextObjectMixin:
 
     FontList = {}
 
 
     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,
     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
 
                                                        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
     ## 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):
     """
 
 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.
 
         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.
 
     
     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,
                  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)
 
             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]
 
         (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)
     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) )
 
             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
     """
     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
@@ -960,7 +1230,7 @@ class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
 
     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
 
     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.
 
     
     The size will scale as the drawing is zoomed.
 
@@ -979,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
     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,
                  Color = "Black",
                  BackgroundColor = None,
                  Family = wx.MODERN,
@@ -997,7 +1267,8 @@ class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
         DrawObject.__init__(self,InForeground)
 
         self.String = String
         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
         self.Size = Size     
         self.Color = Color
         self.BackgroundColor = BackgroundColor
@@ -1015,31 +1286,40 @@ class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
             self.Weight             =  Font.GetWeight()    
 
         # Experimental max font size value on wxGTK2: this works OK on
             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.
         # 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]
 
         
         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.
         ## 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
         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)
         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) )
 
     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
         (X,Y) = WorldToPixel( (self.XY) )
 
@@ -1048,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.
         ## 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:
         dc.SetFont(self.SetFont(Size, self.Family, self.Style, self.Weight, self.Underline, self.FaceName))
         dc.SetTextForeground(self.Color)
         if self.BackgroundColor:
@@ -1069,9 +1348,420 @@ class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
             HTdc.SetBrush(self.HitBrush)
             HTdc.DrawRectanglePointSize(xy, (w, h) )
 
             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):
 
 #---------------------------------------------------------------------------
 class FloatCanvas(wx.Panel):
+    ## fixme: could this be a wx.Window?
     """
     FloatCanvas.py
 
     """
     FloatCanvas.py
 
@@ -1159,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()
         
         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
         del dc
 
         self.HitColorGenerator = None
@@ -1192,7 +1882,7 @@ class FloatCanvas(wx.Panel):
 
         ## create the Hit Test Dicts:
         self.HitDict = None
 
         ## create the Hit Test Dicts:
         self.HitDict = None
-
+        self._HTdc = None
 
         self._DrawList = []
         self._ForeDrawList = []
 
         self._DrawList = []
         self._ForeDrawList = []
@@ -1216,28 +1906,75 @@ class FloatCanvas(wx.Panel):
         self.ObjectUnderMouse = None
         
         # called just to make sure everything is initialized
         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.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 
     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:
             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):
         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
             self.GUIMode = Mode
+            
         else:
         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? 
 
     def MakeHitDict(self):
         ##fixme: Should this just be None if nothing has been bound? 
@@ -1254,13 +1991,13 @@ class FloatCanvas(wx.Panel):
                         EVT_FC_LEAVE_OBJECT: {},
                         }        
             
                         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() )
         """
         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):
         self.GetEventHandler().ProcessEvent(evt)       
 
     def HitTest(self, event, HitEvent):
@@ -1277,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 = 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
                     Object.CallBackFuncs[HitEvent](Object)
                     return True
             return False
@@ -1338,42 +2076,42 @@ class FloatCanvas(wx.Panel):
         if self.GUIMode == "Mouse":
             EventType = EVT_FC_LEFT_DCLICK
             if not self.HitTest(event, EventType):
         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):
 
     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):
 
     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):
 
     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):
 
     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):
 
     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):
 
     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):
 
 
     def LeftDownEvent(self,event):
@@ -1386,17 +2124,18 @@ class FloatCanvas(wx.Panel):
                 Center = self.PixelToWorld( event.GetPosition() )
                 self.Zoom(1/1.5,Center)
             elif self.GUIMode == "Move":
                 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.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):
         else: 
             pass
 
     def LeftUpEvent(self,event):
-       if self.HasCapture():
+        if self.HasCapture():
             self.ReleaseMouse()
         if self.GUIMode:
             if self.GUIMode == "ZoomIn":
             self.ReleaseMouse()
         if self.GUIMode:
             if self.GUIMode == "ZoomIn":
@@ -1419,16 +2158,17 @@ class FloatCanvas(wx.Panel):
                         self.Zoom(1.5,Center)
                     self.StartRBBox = None
             elif self.GUIMode == "Move":
                         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:
                     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.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
 
         else:
             pass
 
@@ -1462,6 +2202,7 @@ class FloatCanvas(wx.Panel):
                     x1,y1 = self.PrevMoveXY
                     x2,y2 = xy_tl
                     w,h = self.PanelSize
                     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
                     if x2 > x1 and y2 > y1:
                         xa = xb = x1
                         ya = yb = y1
@@ -1496,13 +2237,14 @@ class FloatCanvas(wx.Panel):
                         yb = y2 + h
                         wb = w
                         hb = y1 - y2
                         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
                     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)
                         dc.DrawBitmapPoint(self._ForegroundBuffer,xy_tl)
                     else:
                         dc.DrawBitmapPoint(self._Buffer,xy_tl)
@@ -1511,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):
                 ## 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
             else:
                     pass
-            self.RaiseMouseEvent(event,EVT_FC_MOTION)
+            self._RaiseMouseEvent(event,EVT_FC_MOTION)
         else:
             pass
 
         else:
             pass
 
@@ -1529,7 +2271,7 @@ class FloatCanvas(wx.Panel):
             elif self.GUIMode == "Mouse":
                 EventType = EVT_FC_RIGHT_DOWN
                 if not self.HitTest(event, EventType):
             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
         
         else:
             pass
         
@@ -1569,15 +2311,25 @@ class FloatCanvas(wx.Panel):
         else:
            self._ForegroundHTdc = None 
     
         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.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)
         
     def OnPaint(self, event):
         dc = wx.PaintDC(self)
@@ -1601,7 +2353,8 @@ class FloatCanvas(wx.Panel):
         animation, for instance.
         
         """
         animation, for instance.
         
         """
-        #print "In Draw"
+        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)
         ViewPortWorld = ( self.PixelToWorld((0,0)),
         if self.Debug: start = clock()
         ScreenDC =  wx.ClientDC(self)
         ViewPortWorld = ( self.PixelToWorld((0,0)),
@@ -1644,7 +2397,6 @@ class FloatCanvas(wx.Panel):
                               ViewPortBB,
                               self._ForegroundHTdc)
         ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)
                               ViewPortBB,
                               self._ForegroundHTdc)
         ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)
-##        wx.GetApp().Yield(True)
         # If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn
         # This seeems out of place, but it works.
         if self.PrevRBBox:
         # If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn
         # This seeems out of place, but it works.
         if self.PrevRBBox:
@@ -1685,7 +2437,7 @@ class FloatCanvas(wx.Panel):
 ##        else:
 ##            return False
 
 ##        else:
 ##            return False
 
-    def Move(self,shift,CoordType):
+    def MoveImage(self,shift,CoordType):
         """
         move the image in the window.
 
         """
         move the image in the window.
 
@@ -1704,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
         if CoordType == 'Panel':# convert from panel coordinates
             shift = shift * array((-1,1),Float) *self.PanelSize/self.TransformVector
         elif CoordType == 'Pixel': # convert from pixel coordinates
@@ -1712,8 +2465,8 @@ class FloatCanvas(wx.Panel):
         elif CoordType == 'World': # No conversion
             pass
         else:
         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
         self.ViewPortCenter = self.ViewPortCenter + shift 
         self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
         self.TransformVector = array((self.Scale,-self.Scale),Float) * self.MapProjectionVector
@@ -1792,6 +2545,9 @@ class FloatCanvas(wx.Panel):
         ##fixme: Using the list.remove method is kind of slow
         if Object.InForeground:
             self._ForeDrawList.remove(Object)
         ##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
         else:
             self._DrawList.remove(Object)
             self._BackgroundDirty = True
@@ -1842,7 +2598,7 @@ class FloatCanvas(wx.Panel):
             self.BoundingBox = None
             self.ViewPortCenter= array( (0,0), Float)
             self.TransformVector = array( (1,-1), Float)
             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
 
             self.Scale = 1        
         self.BoundingBoxDirty = False
 
@@ -1863,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.
         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):
         """
 
     def ScaleWorldToPixel(self,Lengths):
         """
@@ -1913,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)):
         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()
 
         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",
 
 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):
     for classname in classnames:
         klass = globals()[classname]
         def getaddshapemethod(klass=klass):