git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@27848 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
		
			
				
	
	
		
			1968 lines
		
	
	
		
			77 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1968 lines
		
	
	
		
			77 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
 | |
| try:
 | |
|     from Numeric import array,asarray,Float,cos,pi,sum,minimum,maximum,Int32,zeros
 | |
| except ImportError:
 | |
|     from numarray import array, asarray, Float, cos, pi, sum, minimum, maximum, Int32, zeros
 | |
| 
 | |
| from time import clock, sleep
 | |
| 
 | |
| import wx
 | |
| 
 | |
| import types
 | |
| import os        
 | |
| 
 | |
| import Resources
 | |
| 
 | |
| ## A global variable to hold the Pixels per inch that wxWindows thinks is in use
 | |
| ## This is used for scaling fonts.
 | |
| ## This can't be computed on module __init__, because a wx.App might not have iniitalized yet.
 | |
| global ScreenPPI 
 | |
| 
 | |
| ## a custom Exceptions:
 | |
| 
 | |
| class FloatCanvasException(Exception):
 | |
|     pass
 | |
| 
 | |
| ## All the mouse events
 | |
| #EVT_FC_ENTER_WINDOW = wx.NewEventType()
 | |
| #EVT_FC_LEAVE_WINDOW = wx.NewEventType()
 | |
| EVT_FC_LEFT_DOWN = wx.NewEventType() 
 | |
| EVT_FC_LEFT_UP  = wx.NewEventType()
 | |
| EVT_FC_LEFT_DCLICK = wx.NewEventType() 
 | |
| EVT_FC_MIDDLE_DOWN = wx.NewEventType() 
 | |
| EVT_FC_MIDDLE_UP = wx.NewEventType() 
 | |
| EVT_FC_MIDDLE_DCLICK = wx.NewEventType() 
 | |
| EVT_FC_RIGHT_DOWN = wx.NewEventType() 
 | |
| EVT_FC_RIGHT_UP = wx.NewEventType() 
 | |
| EVT_FC_RIGHT_DCLICK = wx.NewEventType() 
 | |
| EVT_FC_MOTION = wx.NewEventType() 
 | |
| EVT_FC_MOUSEWHEEL = wx.NewEventType() 
 | |
| ## these two are for the hit-test stuff, I never make them real Events
 | |
| EVT_FC_ENTER_OBJECT = wx.NewEventType()
 | |
| EVT_FC_LEAVE_OBJECT = wx.NewEventType()
 | |
| 
 | |
| #def EVT_ENTER_WINDOW( window, function ):
 | |
| #    window.Connect( -1, -1, EVT_FC_ENTER_WINDOW, function ) 
 | |
| #def EVT_LEAVE_WINDOW( window, function ):
 | |
| #    window.Connect( -1, -1,EVT_FC_LEAVE_WINDOW , function ) 
 | |
| def EVT_LEFT_DOWN( window, function ):  
 | |
|     window.Connect( -1, -1,EVT_FC_LEFT_DOWN , function )
 | |
| def EVT_LEFT_UP( window, function ):
 | |
|     window.Connect( -1, -1,EVT_FC_LEFT_UP , function )
 | |
| def EVT_LEFT_DCLICK  ( window, function ):
 | |
|     window.Connect( -1, -1,EVT_FC_LEFT_DCLICK , function )
 | |
| def EVT_MIDDLE_DOWN  ( window, function ):
 | |
|     window.Connect( -1, -1,EVT_FC_MIDDLE_DOWN , function )
 | |
| def EVT_MIDDLE_UP  ( window, function ):
 | |
|     window.Connect( -1, -1,EVT_FC_MIDDLE_UP , function )
 | |
| def EVT_MIDDLE_DCLICK  ( window, function ):
 | |
|     window.Connect( -1, -1,EVT_FC_MIDDLE_DCLICK , function )
 | |
| def EVT_RIGHT_DOWN  ( window, function ):
 | |
|     window.Connect( -1, -1,EVT_FC_RIGHT_DOWN , function )
 | |
| def EVT_RIGHT_UP( window, function ):
 | |
|     window.Connect( -1, -1,EVT_FC_RIGHT_UP , function )
 | |
| def EVT_RIGHT_DCLICK( window, function ):
 | |
|     window.Connect( -1, -1,EVT_FC_RIGHT_DCLICK , function )
 | |
| def EVT_MOTION( window, function ):
 | |
|     window.Connect( -1, -1,EVT_FC_MOTION , function )
 | |
| def EVT_MOUSEWHEEL( window, function ):
 | |
|     window.Connect( -1, -1,EVT_FC_MOUSEWHEEL , function )
 | |
| 
 | |
| class MouseEvent(wx.PyCommandEvent):
 | |
| 
 | |
|     """
 | |
| 
 | |
|     This event class takes a regular wxWindows mouse event as a parameter,
 | |
|     and wraps it so that there is access to all the original methods. This
 | |
|     is similar to subclassing, but you can't subclass a wxWindows event
 | |
| 
 | |
|     The goal is to be able to it just like a regular mouse event.
 | |
| 
 | |
|     It adds the method:
 | |
| 
 | |
|     GetCoords() , which returns and (x,y) tuple in world coordinates.
 | |
| 
 | |
|     Another differnce is that it is a CommandEvent, which propagates up
 | |
|     the window hierarchy until it is handled.
 | |
| 
 | |
|     """
 | |
| 
 | |
|     def __init__(self, EventType, NativeEvent, WinID, Coords = None):
 | |
|         wx.PyCommandEvent.__init__(self)
 | |
| 
 | |
|         self.SetEventType( EventType )
 | |
|         self._NativeEvent = NativeEvent
 | |
|         self.Coords = Coords
 | |
|     
 | |
|     def SetCoords(self,Coords):
 | |
|         self.Coords = Coords
 | |
|         
 | |
|     def GetCoords(self):
 | |
|         return self.Coords
 | |
| 
 | |
|     def __getattr__(self, 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:
 | |
| 
 | |
| ##    """
 | |
| 
 | |
| ##    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
 | |
|     
 | |
| ##    """
 | |
| 
 | |
| ##    def __init__(self):
 | |
| ##        import sys
 | |
| ##        ## figure out the color depth of the screen
 | |
| ##        ## for some bizare reason, thisdoesn't work on OS-X
 | |
| ##        if sys.platform == 'darwin':
 | |
| ##            depth = 24
 | |
| ##        else:
 | |
| ##            b = wx.EmptyBitmap(1,1)
 | |
| ##            depth = b.GetDepth()
 | |
| ##        self.r = 0
 | |
| ##        self.g = 0
 | |
| ##        self.b = 0
 | |
| ##        if depth == 16:
 | |
| ##            self.step = 8
 | |
| ##        elif depth >= 24:
 | |
| ##            self.step = 1
 | |
| ##        else:
 | |
| ##            raise FloatCanvasException("ColorGenerator does not work with depth = %s"%depth )
 | |
| 
 | |
| ##    def GetNextColor(self):
 | |
| ##        step = self.step
 | |
| ##        ##r,g,b = self.r,self.g,self.b
 | |
| ##        self.r += step
 | |
| ##        if self.r > 255:
 | |
| ##            self.r = step
 | |
| ##            self.g += step
 | |
| ##            if self.g > 255:
 | |
| ##                self.g = step
 | |
| ##                self.b += step
 | |
| ##                if self.b > 255:
 | |
| ##                    ## fixme: this should be a derived exception
 | |
| ##                    raise FloatCanvasException("Too many objects in colorgenerator for HitTest")
 | |
| ##        return (self.r,self.g,self.b)
 | |
| 
 | |
| ##    def Reset(self):
 | |
| ##        self.r = 0
 | |
| ##        self.g = 0
 | |
| ##        self.b = 0
 | |
| 
 | |
| def cycleidxs(indexcount, maxvalue, step):
 | |
|     if indexcount == 0:
 | |
|         yield ()
 | |
|     else:
 | |
|         for idx in xrange(0, maxvalue, step):
 | |
|             for tail in cycleidxs(indexcount - 1, maxvalue, step):
 | |
|                 yield (idx, ) + tail
 | |
| 
 | |
| def colorGenerator():
 | |
|     import sys
 | |
|     if sys.platform == 'darwin':
 | |
|         depth = 24
 | |
|     else:
 | |
|         b = wx.EmptyBitmap(1,1)
 | |
|         depth = b.GetDepth()
 | |
|     if depth == 16:
 | |
|         step = 8
 | |
|     elif depth >= 24:
 | |
|         step = 1
 | |
|     else:
 | |
|         raise "ColorGenerator does not work with depth = %s" % depth
 | |
|     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
 | |
| 
 | |
| ##class ObjectSetMixin:
 | |
| ##    """
 | |
| ##    A mix-in class for draw objects that are sets of objects
 | |
| 
 | |
| ##    It contains methods for setting lists of pens and brushes
 | |
| 
 | |
| ##    """
 | |
| ##    def SetPens(self,LineColors,LineStyles,LineWidths):
 | |
| ##        """
 | |
| ##        This method used when an object could have a list of pens, rather than just one
 | |
| ##        It is used for LineSet, and perhaps others in the future.
 | |
| 
 | |
| ##        fixme: this should be in a mixin
 | |
| 
 | |
| ##        fixme: this is really kludgy, there has got to be a better way!
 | |
| 
 | |
| ##        """
 | |
| 
 | |
| ##        length = 1
 | |
| ##        if type(LineColors) == types.ListType:
 | |
| ##            length = len(LineColors)
 | |
| ##        else:
 | |
| ##            LineColors = [LineColors]
 | |
| 
 | |
| ##        if type(LineStyles) == types.ListType:
 | |
| ##            length = len(LineStyles)
 | |
| ##        else:
 | |
| ##            LineStyles = [LineStyles]
 | |
| 
 | |
| ##        if type(LineWidths) == types.ListType:
 | |
| ##            length = len(LineWidths)
 | |
| ##        else:
 | |
| ##            LineWidths = [LineWidths]
 | |
| 
 | |
| ##        if length > 1:
 | |
| ##            if len(LineColors) == 1:
 | |
| ##                LineColors = LineColors*length
 | |
| ##            if len(LineStyles) == 1:
 | |
| ##                LineStyles = LineStyles*length
 | |
| ##            if len(LineWidths) == 1:
 | |
| ##                LineWidths = LineWidths*length
 | |
| 
 | |
| ##        self.Pens = []
 | |
| ##        for (LineColor,LineStyle,LineWidth) in zip(LineColors,LineStyles,LineWidths):
 | |
| ##            if LineColor is None or LineStyle is None:
 | |
| ##                self.Pens.append(wx.TRANSPARENT_PEN)
 | |
| ##                # what's this for?> self.LineStyle = 'Transparent'
 | |
| ##            if not self.PenList.has_key((LineColor,LineStyle,LineWidth)):
 | |
| ##                Pen = wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle])
 | |
| ##                self.Pens.append(Pen)
 | |
| ##            else:
 | |
| ##                self.Pens.append(self.PenList[(LineColor,LineStyle,LineWidth)])
 | |
| ##        if length == 1:
 | |
| ##            self.Pens = self.Pens[0]
 | |
| 
 | |
| 
 | |
| 
 | |
| class DrawObject:
 | |
|     """
 | |
|     This is the base class for all the objects that can be drawn.
 | |
| 
 | |
|     """
 | |
| 
 | |
|     def __init__(self,InForeground  = False):
 | |
|         self.InForeground = InForeground
 | |
| 
 | |
|         self._Canvas = None
 | |
| 
 | |
|         self.HitColor = None
 | |
|         self.CallBackFuncs = {}
 | |
| 
 | |
|         ## these are the defaults
 | |
|         self.HitAble = False
 | |
|         self.HitLine = True
 | |
|         self.HitFill = True
 | |
|         self.MinHitLineWidth = 3
 | |
|         self.HitLineWidth = 3 ## this gets re-set by the subclasses if necessary
 | |
|         
 | |
|     # 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
 | |
|     # does that on it's own. Send me a note if you know!
 | |
| 
 | |
|     BrushList = {
 | |
|             ( None,"Transparent")  : wx.TRANSPARENT_BRUSH,
 | |
|             ("Blue","Solid")       : wx.BLUE_BRUSH,
 | |
|             ("Green","Solid")      : wx.GREEN_BRUSH,
 | |
|             ("White","Solid")      : wx.WHITE_BRUSH,
 | |
|             ("Black","Solid")      : wx.BLACK_BRUSH,
 | |
|             ("Grey","Solid")       : wx.GREY_BRUSH,
 | |
|             ("MediumGrey","Solid") : wx.MEDIUM_GREY_BRUSH,
 | |
|             ("LightGrey","Solid")  : wx.LIGHT_GREY_BRUSH,
 | |
|             ("Cyan","Solid")       : wx.CYAN_BRUSH,
 | |
|             ("Red","Solid")        : wx.RED_BRUSH
 | |
|                     }
 | |
|     PenList = {
 | |
|             (None,"Transparent",1)   : wx.TRANSPARENT_PEN,
 | |
|             ("Green","Solid",1)      : wx.GREEN_PEN,
 | |
|             ("White","Solid",1)      : wx.WHITE_PEN,
 | |
|             ("Black","Solid",1)      : wx.BLACK_PEN,
 | |
|             ("Grey","Solid",1)       : wx.GREY_PEN,
 | |
|             ("MediumGrey","Solid",1) : wx.MEDIUM_GREY_PEN,
 | |
|             ("LightGrey","Solid",1)  : wx.LIGHT_GREY_PEN,
 | |
|             ("Cyan","Solid",1)       : wx.CYAN_PEN,
 | |
|             ("Red","Solid",1)        : wx.RED_PEN
 | |
|             }
 | |
| 
 | |
|     FillStyleList = {
 | |
|             "Transparent"    : wx.TRANSPARENT,
 | |
|             "Solid"          : wx.SOLID,
 | |
|             "BiDiagonalHatch": wx.BDIAGONAL_HATCH,
 | |
|             "CrossDiagHatch" : wx.CROSSDIAG_HATCH,
 | |
|             "FDiagonal_Hatch": wx.FDIAGONAL_HATCH,
 | |
|             "CrossHatch"     : wx.CROSS_HATCH,
 | |
|             "HorizontalHatch": wx.HORIZONTAL_HATCH,
 | |
|             "VerticalHatch"  : wx.VERTICAL_HATCH
 | |
|             }
 | |
| 
 | |
|     LineStyleList = {
 | |
|             "Solid"      : wx.SOLID,
 | |
|             "Transparent": wx.TRANSPARENT,
 | |
|             "Dot"        : wx.DOT,
 | |
|             "LongDash"   : wx.LONG_DASH,
 | |
|             "ShortDash"  : wx.SHORT_DASH,
 | |
|             "DotDash"    : wx.DOT_DASH,
 | |
|             }
 | |
| 
 | |
|     def Bind(self, Event, CallBackFun):
 | |
|         self.CallBackFuncs[Event] = CallBackFun
 | |
|         self.HitAble = True
 | |
|         self._Canvas.UseHitTest = True
 | |
|         if not self._Canvas._HTdc:
 | |
|             self._Canvas.MakeNewHTdc()
 | |
|         if not self.HitColor:
 | |
|             if not self._Canvas.HitColorGenerator:
 | |
|                 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.SetHitBrush(self.HitColor)
 | |
|         # put the object in the hit dict, indexed by it's color
 | |
|         if not self._Canvas.HitDict:
 | |
|             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:
 | |
|             for List in self._Canvas.HitDict.itervalues():
 | |
|                 try:
 | |
|                    List.remove(self)
 | |
|                 except ValueError:
 | |
|                     pass
 | |
|         self.HitAble = False
 | |
| 
 | |
|     def SetBrush(self,FillColor,FillStyle):
 | |
|         if FillColor is None or FillStyle is None:
 | |
|             self.Brush = wx.TRANSPARENT_BRUSH
 | |
|             self.FillStyle = "Transparent"
 | |
|         else:
 | |
|             self.Brush = self.BrushList.setdefault( (FillColor,FillStyle),  wx.Brush(FillColor,self.FillStyleList[FillStyle] ) )
 | |
| 
 | |
|     def SetPen(self,LineColor,LineStyle,LineWidth):
 | |
|         if (LineColor is None) or (LineStyle is None):
 | |
|             self.Pen = wx.TRANSPARENT_PEN
 | |
|             self.LineStyle = 'Transparent'
 | |
|         else:
 | |
|              self.Pen = self.PenList.setdefault( (LineColor,LineStyle,LineWidth),  wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle]) )
 | |
| 
 | |
|     def SetHitBrush(self,HitColor):
 | |
|         if not self.HitFill:
 | |
|             self.HitBrush = wx.TRANSPARENT_BRUSH
 | |
|         else:
 | |
|             self.HitBrush = self.BrushList.setdefault( (HitColor,"solid"),  wx.Brush(HitColor,self.FillStyleList["Solid"] ) )
 | |
| 
 | |
|     def SetHitPen(self,HitColor,LineWidth):
 | |
|         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"]) )
 | |
| 
 | |
|     def PutInBackground(self):
 | |
|         if self._Canvas and self.InForeground:
 | |
|             self._Canvas._ForeDrawList.remove(self)
 | |
|             self._Canvas._DrawList.append(self)
 | |
|             self._Canvas._BackgroundDirty = True
 | |
|             self.InForeground = False
 | |
| 
 | |
|     def PutInForeground(self):
 | |
|         if self._Canvas and (not self.InForeground):
 | |
|             self._Canvas._ForeDrawList.append(self)
 | |
|             self._Canvas._DrawList.remove(self)
 | |
|             self._Canvas._BackgroundDirty = True
 | |
|             self.InForeground = True
 | |
| 
 | |
| class XYObjectMixin:
 | |
|     """
 | |
| 
 | |
|     This is a mixin class that provides some methods suitable for use
 | |
|     with objects that have a single (x,y) coordinate pair.
 | |
| 
 | |
|     """
 | |
| 
 | |
|     def Move(self, Delta ):
 | |
|         """
 | |
| 
 | |
|         Move(Delta): moves the object by delta, where delta is a
 | |
|         (dx,dy) pair. Ideally a Numpy array of shape (2,)
 | |
| 
 | |
|         """
 | |
|         
 | |
|         Delta = asarray(Delta, Float)
 | |
|         self.XY += Delta
 | |
|         self.BoundingBox = self.BoundingBox + Delta
 | |
|         if self._Canvas:
 | |
|             self._Canvas.BoundingBoxDirty = True      
 | |
| 
 | |
| class PointsObjectMixin:
 | |
|     """
 | |
| 
 | |
|     This is a mixin class that provides some methods suitable for use
 | |
|     with objects that have a set of (x,y) coordinate pairs.
 | |
| 
 | |
|     """
 | |
| 
 | |
| ## This is code for the XYMixin object, it needs to be adapeted and tested.
 | |
| ##    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,)
 | |
| 
 | |
| ##        """
 | |
|         
 | |
| ##        Delta = array(Delta, Float)
 | |
| ##        self.XY += Delta
 | |
| ##        self.BoundingBox = self.BoundingBox + Delta##array((self.XY, (self.XY + self.WH)), Float)
 | |
| ##        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)
 | |
|         if self._Canvas:
 | |
|             self._Canvas.BoundingBoxDirty = True
 | |
| 
 | |
| 
 | |
|      
 | |
| class Polygon(DrawObject,PointsObjectMixin):
 | |
| 
 | |
|     """
 | |
| 
 | |
|     The Polygon 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.
 | |
| 
 | |
|     """
 | |
|     def __init__(self,
 | |
|                  Points,
 | |
|                  LineColor = "Black",
 | |
|                  LineStyle = "Solid",
 | |
|                  LineWidth    = 1,
 | |
|                  FillColor    = None,
 | |
|                  FillStyle    = "Solid",
 | |
|                  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.LineColor = LineColor
 | |
|         self.LineStyle = LineStyle
 | |
|         self.LineWidth = LineWidth
 | |
|         self.FillColor = FillColor
 | |
|         self.FillStyle = FillStyle
 | |
| 
 | |
|         self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
 | |
| 
 | |
|         self.SetPen(LineColor,LineStyle,LineWidth)
 | |
|         self.SetBrush(FillColor,FillStyle)
 | |
| 
 | |
|     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel = None, HTdc=None):
 | |
|         Points = WorldToPixel(self.Points)
 | |
|         dc.SetPen(self.Pen)
 | |
|         dc.SetBrush(self.Brush)
 | |
|         dc.DrawPolygon(Points)
 | |
|         if HTdc and self.HitAble:
 | |
|             HTdc.SetPen(self.HitPen)
 | |
|             HTdc.SetBrush(self.HitBrush)
 | |
|             HTdc.DrawPolygon(Points)
 | |
|             
 | |
| ##class PolygonSet(DrawObject):
 | |
| ##    """
 | |
| ##    The PolygonSet class takes a Geometry.Polygon object.
 | |
| ##    so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
 | |
|     
 | |
| ##    it creates a set of line segments, from (x1,y1) to (x2,y2)
 | |
|     
 | |
| ##    """
 | |
|     
 | |
| ##    def __init__(self,PolySet,LineColors,LineStyles,LineWidths,FillColors,FillStyles,InForeground = False):
 | |
| ##        DrawObject.__init__(self, InForeground)
 | |
| 
 | |
| ##        ##fixme: there should be some error checking for everything being the right length.
 | |
| 
 | |
|         
 | |
| ##        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.LineColors = LineColors
 | |
| ##        self.LineStyles = LineStyles
 | |
| ##        self.LineWidths = LineWidths
 | |
| ##        self.FillColors = FillColors
 | |
| ##        self.FillStyles = FillStyles
 | |
| 
 | |
| ##        self.SetPens(LineColors,LineStyles,LineWidths)
 | |
| 
 | |
| ##    #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
 | |
| ##    def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
 | |
| ##        Points = WorldToPixel(self.Points)
 | |
| ##        Points.shape = (-1,4)
 | |
| ##        dc.DrawLineList(Points,self.Pens)
 | |
|  
 | |
| 
 | |
| class Line(DrawObject,PointsObjectMixin):
 | |
|     """
 | |
|     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.
 | |
| 
 | |
|     """
 | |
|     def __init__(self,Points,
 | |
|                  LineColor = "Black",
 | |
|                  LineStyle = "Solid",
 | |
|                  LineWidth    = 1,
 | |
|                  InForeground = False):
 | |
|         DrawObject.__init__(self, InForeground)
 | |
| 
 | |
| 
 | |
|         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.LineColor = LineColor
 | |
|         self.LineStyle = LineStyle
 | |
|         self.LineWidth = LineWidth
 | |
| 
 | |
|         self.SetPen(LineColor,LineStyle,LineWidth)
 | |
| 
 | |
|         self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
 | |
| 
 | |
|             
 | |
|     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
 | |
|         Points = WorldToPixel(self.Points)
 | |
|         dc.SetPen(self.Pen)
 | |
|         dc.DrawLines(Points)
 | |
|         if HTdc and self.HitAble:
 | |
|             HTdc.SetPen(self.HitPen)
 | |
|             HTdc.DrawLines(Points)
 | |
| 
 | |
| ##class LineSet(DrawObject, ObjectSetMixin):
 | |
| ##    """
 | |
| ##    The LineSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
 | |
| ##    so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
 | |
|     
 | |
| ##    it creates a set of line segments, from (x1,y1) to (x2,y2)
 | |
|     
 | |
| ##    """
 | |
|     
 | |
| ##    def __init__(self,Points,LineColors,LineStyles,LineWidths,InForeground = False):
 | |
| ##        DrawObject.__init__(self, InForeground)
 | |
| 
 | |
| ##        NumLines = len(Points) / 2
 | |
| ##        ##fixme: there should be some error checking for everything being the right length.
 | |
| 
 | |
|         
 | |
| ##        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.LineColors = LineColors
 | |
| ##        self.LineStyles = LineStyles
 | |
| ##        self.LineWidths = LineWidths
 | |
| 
 | |
| ##        self.SetPens(LineColors,LineStyles,LineWidths)
 | |
| 
 | |
| ##    #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
 | |
| ##    def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
 | |
| ##        Points = WorldToPixel(self.Points)
 | |
| ##        Points.shape = (-1,4)
 | |
| ##        dc.DrawLineList(Points,self.Pens)
 | |
| 
 | |
| class PointSet(DrawObject):
 | |
|     """
 | |
|     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.
 | |
| 
 | |
|     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.
 | |
| 
 | |
|     In the case of points, the HitLineWidth is used as diameter.
 | |
| 
 | |
|     """
 | |
|     def __init__(self, Points, Color = "Black", Diameter =  1, InForeground = False):
 | |
|         DrawObject.__init__(self,InForeground)
 | |
| 
 | |
|         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.Diameter = Diameter
 | |
| 
 | |
|         self.HitLineWidth = self.MinHitLineWidth
 | |
|         self.SetPen(Color,"Solid",1)
 | |
|         self.SetBrush(Color,"Solid")
 | |
| 
 | |
|     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 DrawD2(self, dc, Points):
 | |
|         # A Little optimization for a diameter2 - point
 | |
|         dc.DrawPointList(Points)
 | |
|         dc.DrawPointList(Points + (1,0))
 | |
|         dc.DrawPointList(Points + (0,1))
 | |
|         dc.DrawPointList(Points + (1,1))
 | |
| 
 | |
|     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
 | |
|         dc.SetPen(self.Pen)
 | |
|         Points = WorldToPixel(self.Points)
 | |
|         if self.Diameter <= 1:
 | |
|             dc.DrawPointList(Points)
 | |
|         elif self.Diameter <= 2:
 | |
|             self.DrawD2(dc, Points)
 | |
|         else:
 | |
|             dc.SetBrush(self.Brush)
 | |
|             radius = int(round(self.Diameter/2))
 | |
|             for xy in Points:
 | |
|                 dc.DrawEllipsePointSize( (xy - radius), (self.Diameter, self.Diameter) )
 | |
|         if HTdc and self.HitAble:
 | |
|             HTdc.SetPen(self.HitPen)
 | |
|             if self.Diameter <= 1:
 | |
|                 HTdc.DrawPointList(Points)
 | |
|             elif self.Diameter <= 2:
 | |
|                 self.DrawD2(HTdc, Points)
 | |
|             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.
 | |
| 
 | |
| ##    Also Fill and line data
 | |
| 
 | |
| ##    """
 | |
| ##    def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,InForeground = False):
 | |
| ##        DrawObject.__init__(self,InForeground)
 | |
| 
 | |
| ##        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)
 | |
| 
 | |
| ##        self.LineColor = LineColor
 | |
| ##        self.LineStyle = LineStyle
 | |
| ##        self.LineWidth = LineWidth
 | |
| ##        self.FillColor = FillColor
 | |
| ##        self.FillStyle = FillStyle
 | |
| 
 | |
| ##        self.SetPen(LineColor,LineStyle,LineWidth)
 | |
| ##        self.SetBrush(FillColor,FillStyle)
 | |
| 
 | |
| ##    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,
 | |
|                  LineColor = "Black",
 | |
|                  LineStyle = "Solid",
 | |
|                  LineWidth    = 1,
 | |
|                  FillColor    = None,
 | |
|                  FillStyle    = "Solid",
 | |
|                  InForeground = False):
 | |
|         
 | |
|         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.LineColor = LineColor
 | |
|         self.LineStyle = LineStyle
 | |
|         self.LineWidth = LineWidth
 | |
|         self.FillColor = FillColor
 | |
|         self.FillStyle = FillStyle
 | |
| 
 | |
|         self.HitLineWidth = max(LineWidth,self.MinHitLineWidth)
 | |
| 
 | |
|         self.SetPen(LineColor,LineStyle,LineWidth)
 | |
|         self.SetBrush(FillColor,FillStyle)
 | |
| 
 | |
| 
 | |
|     def SetUpDraw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc):
 | |
|         dc.SetPen(self.Pen)
 | |
|         dc.SetBrush(self.Brush)
 | |
|         if HTdc and self.HitAble:
 | |
|             HTdc.SetPen(self.HitPen)
 | |
|             HTdc.SetBrush(self.HitBrush)
 | |
|         return ( WorldToPixel(self.XY),
 | |
|                  ScaleWorldToPixel(self.WH) )
 | |
| 
 | |
|     def SetXY(self, x, y):
 | |
|         self.XY = array( (x, y), Float)
 | |
|         self.BoundingBox = array((self.XY, (self.XY + self.WH) ), Float)
 | |
|         if self._Canvas:
 | |
|             self._Canvas.BoundingBoxDirty = True
 | |
| 
 | |
| 
 | |
| class Rectangle(RectEllipse):
 | |
| #    def __init__(*args, **kwargs):
 | |
| #        RectEllipse.__init__(*args, **kwargs)
 | |
| #        raise "an error"
 | |
| 
 | |
|     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
 | |
|         ( XY, WH ) = self.SetUpDraw(dc,
 | |
|                                     WorldToPixel,
 | |
|                                     ScaleWorldToPixel,
 | |
|                                     HTdc)
 | |
|         dc.DrawRectanglePointSize(XY, WH)
 | |
|         if HTdc and self.HitAble:
 | |
|             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,
 | |
|                                     WorldToPixel,
 | |
|                                     ScaleWorldToPixel,
 | |
|                                     HTdc)
 | |
|         dc.DrawEllipsePointSize(XY, WH)
 | |
|         if HTdc and self.HitAble:
 | |
|             HTdc.DrawEllipsePointSize(XY, WH)
 | |
| 
 | |
| class Circle(Ellipse):
 | |
|     def __init__(self, x ,y, Diameter, **kwargs):
 | |
|         RectEllipse.__init__(self ,
 | |
|                              x-Diameter/2.,
 | |
|                              y-Diameter/2.,
 | |
|                              Diameter,
 | |
|                              Diameter,
 | |
|                              **kwargs)
 | |
|         
 | |
| class TextObjectMixin:
 | |
|     """
 | |
| 
 | |
|     A mix in class that holds attributes and methods that are needed by
 | |
|     the Text objects
 | |
| 
 | |
|     """
 | |
|     
 | |
|     ## I'm caching fonts, because on GTK, getting a new font can take a
 | |
|     ## while. However, it gets cleared after every full draw as hanging
 | |
|     ## on to a bunch of large fonts takes a massive amount of memory.
 | |
| 
 | |
|     FontList = {}
 | |
| 
 | |
|     def SetFont(self, Size, Family, Style, Weight, Underline, FaceName):
 | |
|         self.Font = self.FontList.setdefault( (Size,
 | |
|                                                Family,
 | |
|                                                Style,
 | |
|                                                Weight,
 | |
|                                                Underline,
 | |
|                                                FaceName),
 | |
|                                                wx.Font(Size,
 | |
|                                                        Family,
 | |
|                                                        Style, 
 | |
|                                                        Weight,
 | |
|                                                        Underline,
 | |
|                                                        FaceName) )
 | |
|         return self.Font
 | |
| 
 | |
|     ## 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)}
 | |
| 
 | |
| class Text(DrawObject, TextObjectMixin):
 | |
|     """
 | |
|     This class creates a text object, placed at the coordinates,
 | |
|     x,y. the "Position" argument is a two charactor string, indicating
 | |
|     where in relation to the coordinates the string 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 pixels, or in points for printing
 | |
|     (if it ever gets implimented). Those will be the same, If you assume
 | |
|     72 PPI.
 | |
| 
 | |
|     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 is fixed, and does not scale with the drawing.
 | |
| 
 | |
|     The hit-test is done on the entire text extent
 | |
| 
 | |
|     """
 | |
|     
 | |
|     def __init__(self,String,x,y,
 | |
|                  Size =  12,
 | |
|                  Color = "Black",
 | |
|                  BackgroundColor = None,
 | |
|                  Family = wx.MODERN,
 | |
|                  Style = wx.NORMAL,
 | |
|                  Weight = wx.NORMAL,
 | |
|                  Underline = False,
 | |
|                  Position = 'tl',
 | |
|                  InForeground = False,
 | |
|                  Font = None):
 | |
|         
 | |
|         DrawObject.__init__(self,InForeground)
 | |
| 
 | |
|         self.String = String
 | |
|         # Input size in in Pixels, compute points size from PPI info.
 | |
|         # fixme: for printing, we'll have to do something a little different
 | |
|         self.Size = int(round(72.0 * Size / ScreenPPI))
 | |
| 
 | |
|         self.Color = Color
 | |
|         self.BackgroundColor = BackgroundColor
 | |
| 
 | |
|         if not Font:
 | |
|             FaceName = ''
 | |
|         else:
 | |
|             FaceName           =  Font.GetFaceName()           
 | |
|             Family             =  Font.GetFamily()
 | |
|             Size               =  Font.GetPointSize()          
 | |
|             Style              =  Font.GetStyle()
 | |
|             Underlined         =  Font.GetUnderlined()         
 | |
|             Weight             =  Font.GetWeight()
 | |
|         self.SetFont(Size, Family, Style, Weight, Underline, FaceName)
 | |
| 
 | |
|         self.BoundingBox = array(((x,y),(x,y)),Float)
 | |
| 
 | |
|         self.XY = ( x,y )
 | |
| 
 | |
| 	# use a memDC --  ScreenDC doesn't work with 2.5.1 and GTK2
 | |
|         #dc = wx.MemoryDC()
 | |
|         #bitmap = wx.EmptyBitmap(1, 1)
 | |
|         #dc.SelectObject(bitmap)  		
 | |
|         #dc.SetFont(self.Font)
 | |
|         #(self.TextWidth, self.TextHeight) = dc.GetTextExtent(self.String)
 | |
|         (self.TextWidth, self.TextHeight) = (None, None)
 | |
|         self.ShiftFun = self.ShiftFunDict[Position]
 | |
| 
 | |
|     def SetXY(self, x, y):
 | |
|         self.XY = ( x,y )
 | |
|         self.BoundingBox = array((self.XY, self.XY),Float)
 | |
|         if self._Canvas:
 | |
|             self._Canvas.BoundingBoxDirty = True
 | |
| 
 | |
|     def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
 | |
|         XY = WorldToPixel(self.XY)
 | |
|         dc.SetFont(self.Font)
 | |
|         dc.SetTextForeground(self.Color)
 | |
|         if self.BackgroundColor:
 | |
|             dc.SetBackgroundMode(wx.SOLID)
 | |
|             dc.SetTextBackground(self.BackgroundColor)
 | |
|         else:
 | |
|             dc.SetBackgroundMode(wx.TRANSPARENT)
 | |
|         if self.TextWidth is None or self.TextHeight is None:
 | |
|             (self.TextWidth, self.TextHeight) = dc.GetTextExtent(self.String)
 | |
|         XY = self.ShiftFun(XY[0], XY[1], self.TextWidth, self.TextHeight)
 | |
|         dc.DrawTextPoint(self.String, XY)
 | |
|         if HTdc and self.HitAble:
 | |
|             HTdc.SetPen(self.HitPen)
 | |
|             HTdc.SetBrush(self.HitBrush)
 | |
|             HTdc.DrawRectanglePointSize(XY, (self.TextWidth, self.TextHeight) )
 | |
| 
 | |
| class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin):
 | |
|     """
 | |
|     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
 | |
|     charactor string, indicating where in relation to the coordinates
 | |
|     the string 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.DECORATI: A decorative font. 
 | |
|             wx.ROMAN: A formal, serif font. 
 | |
|             wx.SCRIPT: A handwriting font. 
 | |
|             wx.SWISS: A sans-serif font. 
 | |
|             wx.MODERN: A fixed pitch font.
 | |
|             
 | |
|         NOTE: these are only as good as the wxWindows defaults, which aren't so good.
 | |
|         
 | |
|     Style:
 | |
|         One of wx.NORMAL, wx.SLANT and wx.ITALIC.
 | |
|         
 | |
|     Weight:
 | |
|         One of wx.NORMAL, wx.LIGHT and wx.BOLD.
 | |
|         
 | |
|     Underline:
 | |
|         The value can be True or False. At present this may have an an
 | |
|         effect on Windows only.
 | |
| 
 | |
|     Alternatively, you can set the kw arg: Font, to a wx.Font, and the
 | |
|     above will be ignored. The size of the font you specify will be
 | |
|     ignored, but the rest of it's attributes will be preserved.
 | |
|     
 | |
|     The size will scale as the drawing is zoomed.
 | |
| 
 | |
|     Bugs/Limitations:
 | |
| 
 | |
|     As fonts are scaled, the do end up a little different, so you don't
 | |
|     get exactly the same picture as you scale up and doen, but it's
 | |
|     pretty darn close.
 | |
|     
 | |
|     On wxGTK1 on my Linux system, at least, using a font of over about
 | |
|     3000 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
 | |
|     3000 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 text extent. This could be made
 | |
|     optional, but I havn't gotten around to it.
 | |
| 
 | |
|     """
 | |
|     
 | |
|     def __init__(self, String, x, y , Size,
 | |
|                  Color = "Black",
 | |
|                  BackgroundColor = None,
 | |
|                  Family = wx.MODERN,
 | |
|                  Style = wx.NORMAL,
 | |
|                  Weight = wx.NORMAL,
 | |
|                  Underline = False,
 | |
|                  Position = 'tl',
 | |
|                  Font = None,
 | |
|                  InForeground = False):
 | |
|         
 | |
|         DrawObject.__init__(self,InForeground)
 | |
| 
 | |
|         self.String = String
 | |
|         self.XY = array( (x, y), Float)
 | |
|         self.Size = Size     
 | |
|         self.Color = Color
 | |
|         self.BackgroundColor = BackgroundColor
 | |
|         self.Family = Family   
 | |
|         self.Style = Style    
 | |
|         self.Weight = Weight   
 | |
|         self.Underline = Underline
 | |
|         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 any larger, there is a crash, with the
 | |
|         # message: The application 'FloatCanvasDemo.py' lost its
 | |
|         # connection to the display :0.0; most likely the X server was
 | |
|         # shut down or you killed/destroyed the application.
 | |
|         self.MaxSize = 2750
 | |
|         
 | |
|         self.ShiftFun = self.ShiftFunDict[Position]
 | |
| 
 | |
|         ## Compute the BB
 | |
|         ## 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
 | |
|         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)
 | |
|         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) )
 | |
| 
 | |
|         # 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.
 | |
|         if Size > self.MaxSize:
 | |
|             Size = self.MaxSize
 | |
|         dc.SetFont(self.SetFont(Size, self.Family, self.Style, self.Weight, self.Underline, self.FaceName))
 | |
|         dc.SetTextForeground(self.Color)
 | |
|         if self.BackgroundColor:
 | |
|             dc.SetBackgroundMode(wx.SOLID)
 | |
|             dc.SetTextBackground(self.BackgroundColor)
 | |
|         else:
 | |
|             dc.SetBackgroundMode(wx.TRANSPARENT)
 | |
|         (w,h) = dc.GetTextExtent(self.String)
 | |
|         # compute the shift, and adjust the coordinates, if neccesary
 | |
|         # This had to be put in here, because it changes with Zoom, as
 | |
|         # fonts don't scale exactly.
 | |
|         xy = self.ShiftFun(X, Y, w, h)
 | |
| 
 | |
|         dc.DrawTextPoint(self.String, xy)
 | |
|         if HTdc and self.HitAble:
 | |
|             HTdc.SetPen(self.HitPen)
 | |
|             HTdc.SetBrush(self.HitBrush)
 | |
|             HTdc.DrawRectanglePointSize(xy, (w, h) )
 | |
| 
 | |
| 
 | |
| #---------------------------------------------------------------------------
 | |
| class FloatCanvas(wx.Panel):
 | |
|     """
 | |
|     FloatCanvas.py
 | |
| 
 | |
|     This is a high level window for drawing maps and anything else in an
 | |
|     arbitrary coordinate system.
 | |
| 
 | |
|     The goal is to provide a convenient way to draw stuff on the screen
 | |
|     without having to deal with handling OnPaint events, converting to pixel
 | |
|     coordinates, knowing about wxWindows brushes, pens, and colors, etc. It
 | |
|     also provides virtually unlimited zooming and scrolling
 | |
| 
 | |
|     I am using it for two things:
 | |
|     1) general purpose drawing in floating point coordinates
 | |
|     2) displaying map data in Lat-long coordinates
 | |
| 
 | |
|     If the projection is set to None, it will draw in general purpose
 | |
|     floating point coordinates. If the projection is set to 'FlatEarth', it
 | |
|     will draw a FlatEarth projection, centered on the part of the map that
 | |
|     you are viewing. You can also pass in your own projection function.
 | |
| 
 | |
|     It is double buffered, so re-draws after the window is uncovered by something
 | |
|     else are very quick.
 | |
| 
 | |
|     It relies on NumPy, which is needed for speed (maybe, I havn't profiled it)
 | |
| 
 | |
|     Bugs and Limitations:
 | |
|         Lots: patches, fixes welcome
 | |
| 
 | |
|     For Map drawing: It ignores the fact that the world is, in fact, a
 | |
|     sphere, so it will do strange things if you are looking at stuff near
 | |
|     the poles or the date line. so far I don't have a need to do that, so I
 | |
|     havn't bothered to add any checks for that yet.
 | |
| 
 | |
|     Zooming:
 | |
|     I have set no zoom limits. What this means is that if you zoom in really 
 | |
|     far, you can get integer overflows, and get wierd results. It
 | |
|     doesn't seem to actually cause any problems other than wierd output, at
 | |
|     least when I have run it.
 | |
| 
 | |
|     Speed:
 | |
|     I have done a couple of things to improve speed in this app. The one
 | |
|     thing I have done is used NumPy Arrays to store the coordinates of the
 | |
|     points of the objects. This allowed me to use array oriented functions
 | |
|     when doing transformations, and should provide some speed improvement
 | |
|     for objects with a lot of points (big polygons, polylines, pointsets).
 | |
| 
 | |
|     The real slowdown comes when you have to draw a lot of objects, because
 | |
|     you have to call the wx.DC.DrawSomething call each time. This is plenty
 | |
|     fast for tens of objects, OK for hundreds of objects, but pretty darn
 | |
|     slow for thousands of objects.
 | |
| 
 | |
|     The solution is to be able to pass some sort of object set to the DC
 | |
|     directly. I've used DC.DrawPointList(Points), and it helped a lot with
 | |
|     drawing lots of points. I havn't got a LineSet type object, so I havn't
 | |
|     used DC.DrawLineList yet. I'd like to get a full set of DrawStuffList()
 | |
|     methods implimented, and then I'd also have a full set of Object sets
 | |
|     that could take advantage of them. I hope to get to it some day.
 | |
| 
 | |
|     Mouse Events:
 | |
| 
 | |
|     At this point, there are a full set of custom mouse events. They are
 | |
|     just like the rebulsr mouse events, but include an extra attribute:
 | |
|     Event.GetCoords(), that returns the (x,y) position in world
 | |
|     coordinates, as a length-2 NumPy vector of Floats.
 | |
|     
 | |
|     Copyright: Christopher Barker
 | |
| 
 | |
|     License: Same as the version of wxPython you are using it with
 | |
| 
 | |
|     Please let me know if you're using this!!!
 | |
| 
 | |
|     Contact me at:
 | |
| 
 | |
|     Chris.Barker@noaa.gov
 | |
| 
 | |
|     """ 
 | |
|     
 | |
|     def __init__(self, parent, id = -1,
 | |
|                  size = wx.DefaultSize,
 | |
|                  ProjectionFun = None,
 | |
|                  BackgroundColor = "WHITE",
 | |
|                  Debug = False):
 | |
| 
 | |
|         wx.Panel.__init__( self, parent, id, wx.DefaultPosition, size)
 | |
|         
 | |
|         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
 | |
|         del dc
 | |
| 
 | |
|         self.HitColorGenerator = None
 | |
|         self.UseHitTest = None
 | |
| 
 | |
|         self.NumBetweenBlits = 500
 | |
| 
 | |
|         self.BackgroundBrush = wx.Brush(BackgroundColor,wx.SOLID)
 | |
| 
 | |
|         self.Debug = Debug
 | |
| 
 | |
|         wx.EVT_PAINT(self, self.OnPaint)
 | |
|         wx.EVT_SIZE(self, self.OnSize)
 | |
|         
 | |
|         wx.EVT_LEFT_DOWN(self, self.LeftDownEvent ) 
 | |
|         wx.EVT_LEFT_UP(self, self.LeftUpEvent ) 
 | |
|         wx.EVT_LEFT_DCLICK(self, self.LeftDoubleClickEvent ) 
 | |
|         wx.EVT_MIDDLE_DOWN(self, self.MiddleDownEvent ) 
 | |
|         wx.EVT_MIDDLE_UP(self, self.MiddleUpEvent ) 
 | |
|         wx.EVT_MIDDLE_DCLICK(self, self.MiddleDoubleClickEvent ) 
 | |
|         wx.EVT_RIGHT_DOWN(self, self.RightDownEvent)
 | |
|         wx.EVT_RIGHT_UP(self, self.RightUpEvent ) 
 | |
|         wx.EVT_RIGHT_DCLICK(self, self.RightDoubleCLickEvent ) 
 | |
|         wx.EVT_MOTION(self, self.MotionEvent ) 
 | |
|         wx.EVT_MOUSEWHEEL(self, self.WheelEvent ) 
 | |
| 
 | |
|         ## CHB: I'm leaving these out for now.
 | |
|         #wx.EVT_ENTER_WINDOW(self, self. ) 
 | |
|         #wx.EVT_LEAVE_WINDOW(self, self. ) 
 | |
| 
 | |
|         ## create the Hit Test Dicts:
 | |
|         self.HitDict = None
 | |
| 
 | |
| 
 | |
|         self._DrawList = []
 | |
|         self._ForeDrawList = []
 | |
|         self._ForegroundBuffer = None
 | |
|         self.BoundingBox = None
 | |
|         self.BoundingBoxDirty = False
 | |
|         self.ViewPortCenter= array( (0,0), Float)
 | |
| 
 | |
|         self.SetProjectionFun(ProjectionFun)
 | |
|         
 | |
|         self.MapProjectionVector = array( (1,1), Float) # No Projection to start!
 | |
|         self.TransformVector = array( (1,-1), Float) # default Transformation
 | |
|         
 | |
|         self.Scale = 1
 | |
| 
 | |
|         self.GUIMode = None
 | |
|         self.StartRBBox = None
 | |
|         self.PrevRBBox = None
 | |
|         self.StartMove = None
 | |
|         self.PrevMoveXY = None
 | |
|         self.ObjectUnderMouse = None
 | |
|         
 | |
|         # called just to make sure everything is initialized
 | |
|         self.OnSize(None)
 | |
| 
 | |
|         self.InHereNum = 0
 | |
| 
 | |
|     def SetProjectionFun(self,ProjectionFun):
 | |
|         if ProjectionFun == 'FlatEarth':
 | |
|             self.ProjectionFun = self.FlatEarthProjection 
 | |
|         elif type(ProjectionFun) == types.FunctionType:
 | |
|             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')
 | |
| 
 | |
|     def FlatEarthProjection(self,CenterPoint):
 | |
|         return array((cos(pi*CenterPoint[1]/180),1),Float)
 | |
| 
 | |
|     def SetMode(self,Mode):
 | |
|         if Mode in ["ZoomIn","ZoomOut","Move","Mouse",None]:
 | |
|             self.GUIMode = Mode
 | |
|         else:
 | |
|             raise FloatCanvasException('"%s" is Not a valid Mode'%Mode)
 | |
| 
 | |
|     def MakeHitDict(self):
 | |
|         ##fixme: Should this just be None if nothing has been bound? 
 | |
|         self.HitDict = {EVT_FC_LEFT_DOWN: {},
 | |
|                         EVT_FC_LEFT_UP: {},
 | |
|                         EVT_FC_LEFT_DCLICK: {},
 | |
|                         EVT_FC_MIDDLE_DOWN: {},
 | |
|                         EVT_FC_MIDDLE_UP: {},
 | |
|                         EVT_FC_MIDDLE_DCLICK: {},
 | |
|                         EVT_FC_RIGHT_DOWN: {},
 | |
|                         EVT_FC_RIGHT_UP: {},
 | |
|                         EVT_FC_RIGHT_DCLICK: {},
 | |
|                         EVT_FC_ENTER_OBJECT: {},
 | |
|                         EVT_FC_LEAVE_OBJECT: {},
 | |
|                         }        
 | |
|             
 | |
|     def RaiseMouseEvent(self, Event, EventType):
 | |
|         """
 | |
|         This is called in various other places to raise a Mouse Event
 | |
|         """
 | |
|         #print "in Raise Mouse Event", Event
 | |
|         pt = self.PixelToWorld( Event.GetPosition() )
 | |
|         evt = MouseEvent(EventType, Event, self.GetId(), pt)
 | |
|         self.GetEventHandler().ProcessEvent(evt)       
 | |
| 
 | |
|     def HitTest(self, event, HitEvent):
 | |
|         if self.HitDict:
 | |
|             # check if there are any objects in the dict for this event
 | |
|             if self.HitDict[ HitEvent ]:
 | |
|                 xy = event.GetPosition()
 | |
|                 if self._ForegroundHTdc:
 | |
|                     hitcolor = self._ForegroundHTdc.GetPixelPoint( xy )
 | |
|                 else:
 | |
|                     hitcolor = self._HTdc.GetPixelPoint( xy )
 | |
|                 color = ( hitcolor.Red(), hitcolor.Green(), hitcolor.Blue() )
 | |
|                 if color in self.HitDict[ HitEvent ]:
 | |
|                     Object = self.HitDict[ HitEvent ][color]
 | |
|                     ## Add the hit coords to the Object
 | |
|                     Object.HitCoords = self.PixelToWorld( xy )
 | |
|                     Object.CallBackFuncs[HitEvent](Object)
 | |
|                     return True
 | |
|             return False
 | |
| 
 | |
|     def MouseOverTest(self, event):
 | |
|         ##fixme: Can this be cleaned up?
 | |
|         if self.HitDict:
 | |
|             xy = event.GetPosition()
 | |
|             if self._ForegroundHTdc:
 | |
|                 hitcolor = self._ForegroundHTdc.GetPixelPoint( xy )
 | |
|             else:
 | |
|                 hitcolor = self._HTdc.GetPixelPoint( xy )
 | |
|             color = ( hitcolor.Red(), hitcolor.Green(), hitcolor.Blue() )
 | |
|             OldObject = self.ObjectUnderMouse
 | |
|             ObjectCallbackCalled = False
 | |
|             if color in self.HitDict[ EVT_FC_ENTER_OBJECT ]:
 | |
|                 Object = self.HitDict[ EVT_FC_ENTER_OBJECT][color]
 | |
|                 if (OldObject is None):
 | |
|                     try:
 | |
|                         Object.CallBackFuncs[EVT_FC_ENTER_OBJECT](Object)
 | |
|                         ObjectCallbackCalled =  True
 | |
|                     except KeyError:
 | |
|                         pass # this means the enter event isn't bound for that object
 | |
|                 elif OldObject == Object: # the mouse is still on the same object
 | |
|                     pass
 | |
|                     ## Is the mouse on a differnt object as it was...
 | |
|                 elif not (Object == OldObject):
 | |
|                     # call the leave object callback
 | |
|                     try:
 | |
|                         OldObject.CallBackFuncs[EVT_FC_LEAVE_OBJECT](OldObject)
 | |
|                         ObjectCallbackCalled =  True
 | |
|                     except KeyError:
 | |
|                         pass # this means the leave event isn't bound for that object
 | |
|                     try:
 | |
|                         Object.CallBackFuncs[EVT_FC_ENTER_OBJECT](Object)
 | |
|                         ObjectCallbackCalled =  True
 | |
|                     except KeyError:
 | |
|                         pass # this means the enter event isn't bound for that object
 | |
|                     ## set the new object under mouse
 | |
|                 self.ObjectUnderMouse = Object
 | |
|             elif color in self.HitDict[ EVT_FC_LEAVE_OBJECT ]:
 | |
|                 Object = self.HitDict[ EVT_FC_LEAVE_OBJECT][color]
 | |
|                 self.ObjectUnderMouse = Object
 | |
|             else:
 | |
|                 # no objects under mouse bound to mouse-over events
 | |
|                 self.ObjectUnderMouse = None
 | |
|                 if OldObject:
 | |
|                     try:
 | |
|                         OldObject.CallBackFuncs[EVT_FC_LEAVE_OBJECT](OldObject)
 | |
|                         ObjectCallbackCalled =  True
 | |
|                     except KeyError:
 | |
|                         pass # this means the leave event isn't bound for that object
 | |
|             return ObjectCallbackCalled
 | |
| 
 | |
| 
 | |
|     ## fixme: There is a lot of repeated code here
 | |
|     ##        Is there a better way?            
 | |
|     def LeftDoubleClickEvent(self,event):
 | |
|         if self.GUIMode == "Mouse":
 | |
|             EventType = EVT_FC_LEFT_DCLICK
 | |
|             if not self.HitTest(event, EventType):
 | |
|                 self.RaiseMouseEvent(event, EventType)
 | |
| 
 | |
| 
 | |
|     def MiddleDownEvent(self,event):
 | |
|         if self.GUIMode == "Mouse":
 | |
|             EventType = EVT_FC_MIDDLE_DOWN
 | |
|             if not self.HitTest(event, EventType):
 | |
|                 self.RaiseMouseEvent(event, EventType)
 | |
| 
 | |
|     def MiddleUpEvent(self,event):
 | |
|         if self.GUIMode == "Mouse":
 | |
|             EventType = EVT_FC_MIDDLE_UP
 | |
|             if not self.HitTest(event, EventType):
 | |
|                 self.RaiseMouseEvent(event, EventType)
 | |
| 
 | |
|     def MiddleDoubleClickEvent(self,event):
 | |
|         if self.GUIMode == "Mouse":
 | |
|             EventType = EVT_FC_MIDDLE_DCLICK
 | |
|             if not self.HitTest(event, EventType):
 | |
|                 self.RaiseMouseEvent(event, EventType)
 | |
| 
 | |
|     def RightUpEvent(self,event):
 | |
|         if self.GUIMode == "Mouse":
 | |
|             EventType = EVT_FC_RIGHT_UP
 | |
|             if not self.HitTest(event, EventType):
 | |
|                 self.RaiseMouseEvent(event, EventType)
 | |
| 
 | |
|     def RightDoubleCLickEvent(self,event):
 | |
|         if self.GUIMode == "Mouse":
 | |
|             EventType = EVT_FC_RIGHT_DCLICK
 | |
|             if not self.HitTest(event, EventType):
 | |
|                 self.RaiseMouseEvent(event, EventType)
 | |
| 
 | |
|     def WheelEvent(self,event):
 | |
|         if self.GUIMode == "Mouse":
 | |
|             self.RaiseMouseEvent(event, EVT_FC_MOUSEWHEEL)
 | |
| 
 | |
| 
 | |
|     def LeftDownEvent(self,event):
 | |
|         if self.GUIMode:
 | |
|             if self.GUIMode == "ZoomIn":
 | |
|                 self.StartRBBox = array( event.GetPosition() )
 | |
|                 self.PrevRBBox = None
 | |
|                 self.CaptureMouse()
 | |
|             elif self.GUIMode == "ZoomOut":
 | |
|                 Center = self.PixelToWorld( event.GetPosition() )
 | |
|                 self.Zoom(1/1.5,Center)
 | |
|             elif self.GUIMode == "Move":
 | |
|                 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)
 | |
|         else: 
 | |
|             pass
 | |
| 
 | |
|     def LeftUpEvent(self,event):
 | |
|     	if self.HasCapture():
 | |
|             self.ReleaseMouse()
 | |
|         if self.GUIMode:
 | |
|             if self.GUIMode == "ZoomIn":
 | |
|                 if event.LeftUp() and not self.StartRBBox is None:
 | |
|                     self.PrevRBBox = None
 | |
|                     EndRBBox = event.GetPosition()
 | |
|                     StartRBBox = self.StartRBBox
 | |
|                     # if mouse has moved less that ten pixels, don't use the box.
 | |
|                     if ( abs(StartRBBox[0] - EndRBBox[0]) > 10
 | |
|                          and abs(StartRBBox[1] - EndRBBox[1]) > 10 ):
 | |
|                         EndRBBox = self.PixelToWorld(EndRBBox)
 | |
|                         StartRBBox = self.PixelToWorld(StartRBBox)
 | |
|                         BB = array(((min(EndRBBox[0],StartRBBox[0]),
 | |
|                                      min(EndRBBox[1],StartRBBox[1])),
 | |
|                                     (max(EndRBBox[0],StartRBBox[0]),
 | |
|                                      max(EndRBBox[1],StartRBBox[1]))),Float)
 | |
|                         self.ZoomToBB(BB)
 | |
|                     else:
 | |
|                         Center = self.PixelToWorld(StartRBBox)
 | |
|                         self.Zoom(1.5,Center)
 | |
|                     self.StartRBBox = None
 | |
|             elif self.GUIMode == "Move":
 | |
|                 if not self.StartMove is None:
 | |
|                     StartMove = self.StartMove
 | |
|                     EndMove = array((event.GetX(),event.GetY()))
 | |
|                     if sum((StartMove-EndMove)**2) > 16:
 | |
|                         self.Move(StartMove-EndMove,'Pixel')
 | |
|                     self.StartMove = None
 | |
|             elif self.GUIMode == "Mouse":
 | |
|                 EventType = EVT_FC_LEFT_UP
 | |
|                 if not self.HitTest(event, EventType):
 | |
|                    self.RaiseMouseEvent(event, EventType)
 | |
|         else:
 | |
|             pass
 | |
| 
 | |
|     def MotionEvent(self,event):
 | |
|         if self.GUIMode:
 | |
|             if self.GUIMode == "ZoomIn":
 | |
|                 if event.Dragging() and event.LeftIsDown() and not (self.StartRBBox is None):
 | |
|                     xy0 = self.StartRBBox
 | |
|                     xy1 = array( event.GetPosition() )
 | |
|                     wh  = abs(xy1 - xy0)
 | |
|                     wh[0] = max(wh[0], int(wh[1]*self.AspectRatio))
 | |
|                     wh[1] = int(wh[0] / self.AspectRatio)
 | |
|                     xy_c = (xy0 + xy1) / 2
 | |
|                     dc = wx.ClientDC(self)
 | |
|                     dc.BeginDrawing()
 | |
|                     dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH))
 | |
|                     dc.SetBrush(wx.TRANSPARENT_BRUSH)
 | |
|                     dc.SetLogicalFunction(wx.XOR)
 | |
|                     if self.PrevRBBox:
 | |
|                         dc.DrawRectanglePointSize(*self.PrevRBBox)
 | |
|                     self.PrevRBBox = ( xy_c - wh/2, wh )
 | |
|                     dc.DrawRectanglePointSize( *self.PrevRBBox )
 | |
|                     dc.EndDrawing()
 | |
|             elif self.GUIMode == "Move":
 | |
|                 if event.Dragging() and event.LeftIsDown() and not self.StartMove is None:
 | |
|                     xy1 = array( event.GetPosition() )
 | |
|                     wh = self.PanelSize
 | |
|                     xy_tl = xy1 - self.StartMove
 | |
|                     dc = wx.ClientDC(self)
 | |
|                     dc.BeginDrawing()
 | |
|                     x1,y1 = self.PrevMoveXY
 | |
|                     x2,y2 = xy_tl
 | |
|                     w,h = self.PanelSize
 | |
|                     if x2 > x1 and y2 > y1:
 | |
|                         xa = xb = x1
 | |
|                         ya = yb = y1
 | |
|                         wa = w
 | |
|                         ha = y2 - y1
 | |
|                         wb = x2-  x1
 | |
|                         hb = h
 | |
|                     elif x2 > x1 and y2 <= y1:
 | |
|                         xa = x1
 | |
|                         ya = y1
 | |
|                         wa = x2 - x1
 | |
|                         ha = h
 | |
|                         xb = x1
 | |
|                         yb = y2 + h
 | |
|                         wb = w
 | |
|                         hb = y1 - y2
 | |
|                     elif x2 <= x1 and y2 > y1:
 | |
|                         xa = x1
 | |
|                         ya = y1
 | |
|                         wa = w
 | |
|                         ha = y2 - y1
 | |
|                         xb = x2 + w
 | |
|                         yb = y1
 | |
|                         wb = x1 - x2
 | |
|                         hb = h - y2 + y1 
 | |
|                     elif x2 <= x1 and y2 <= y1:
 | |
|                         xa = x2 + w
 | |
|                         ya = y1
 | |
|                         wa = x1 - x2
 | |
|                         ha = h
 | |
|                         xb = x1
 | |
|                         yb = y2 + h
 | |
|                         wb = w
 | |
|                         hb = y1 - y2
 | |
|                     
 | |
|                     dc.SetPen(wx.TRANSPARENT_PEN)
 | |
|                     dc.SetBrush(self.BackgroundBrush)
 | |
|                     dc.DrawRectangle(xa, ya, wa, ha)
 | |
|                     dc.DrawRectangle(xb, yb, wb, hb)
 | |
|                     self.PrevMoveXY = xy_tl
 | |
|                     if self._ForegroundBuffer:
 | |
|                         dc.DrawBitmapPoint(self._ForegroundBuffer,xy_tl)
 | |
|                     else:
 | |
|                         dc.DrawBitmapPoint(self._Buffer,xy_tl)
 | |
|                     dc.EndDrawing()
 | |
|             elif self.GUIMode == "Mouse":
 | |
|                 ## 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)
 | |
|             else:
 | |
|                     pass
 | |
|             self.RaiseMouseEvent(event,EVT_FC_MOTION)
 | |
|         else:
 | |
|             pass
 | |
| 
 | |
|     def RightDownEvent(self,event):
 | |
|         if self.GUIMode:
 | |
|             if self.GUIMode == "ZoomIn":
 | |
|                 Center = self.PixelToWorld((event.GetX(),event.GetY()))
 | |
|                 self.Zoom(1/1.5,Center)
 | |
|             elif self.GUIMode == "ZoomOut":
 | |
|                 Center = self.PixelToWorld((event.GetX(),event.GetY()))
 | |
|                 self.Zoom(1.5,Center)
 | |
|             elif self.GUIMode == "Mouse":
 | |
|                 EventType = EVT_FC_RIGHT_DOWN
 | |
|                 if not self.HitTest(event, EventType):
 | |
|                    self.RaiseMouseEvent(event, EventType)
 | |
|         else:
 | |
|             pass
 | |
|         
 | |
|     def MakeNewBuffers(self):
 | |
|         self._BackgroundDirty = True
 | |
|         # Make new offscreen bitmap:
 | |
|         self._Buffer = wx.EmptyBitmap(*self.PanelSize)
 | |
|         #dc = wx.MemoryDC()
 | |
|         #dc.SelectObject(self._Buffer)
 | |
|         #dc.Clear()
 | |
|         if self._ForeDrawList:
 | |
|             self._ForegroundBuffer = wx.EmptyBitmap(*self.PanelSize)
 | |
|         else:
 | |
|             self._ForegroundBuffer = None
 | |
|         if self.UseHitTest:
 | |
|             self.MakeNewHTdc()
 | |
|         else:
 | |
|             self._HTdc = None
 | |
|             self._ForegroundHTdc = None
 | |
| 
 | |
|     def MakeNewHTdc(self):
 | |
|         ## Note: While it's considered a "bad idea" to keep a
 | |
|         ## MemoryDC around I'm doing it here because a wx.Bitmap
 | |
|         ## doesn't have a GetPixel method so a DC is needed to do
 | |
|         ## the hit-test. It didn't seem like a good idea to re-create
 | |
|         ## a wx.MemoryDC on every single mouse event, so I keep it
 | |
|         ## around instead
 | |
|         self._HTdc = wx.MemoryDC()
 | |
|         self._HTBitmap = wx.EmptyBitmap(*self.PanelSize) 
 | |
|         self._HTdc.SelectObject( self._HTBitmap )
 | |
|         self._HTdc.SetBackground(wx.BLACK_BRUSH)
 | |
|         if self._ForeDrawList:
 | |
|             self._ForegroundHTdc = wx.MemoryDC()
 | |
|             self._ForegroundHTBitmap = wx.EmptyBitmap(*self.PanelSize) 
 | |
|             self._ForegroundHTdc.SelectObject( self._ForegroundHTBitmap )
 | |
|             self._ForegroundHTdc.SetBackground(wx.BLACK_BRUSH)
 | |
|         else:
 | |
|            self._ForegroundHTdc = None 
 | |
|     
 | |
|     def OnSize(self,event):
 | |
|         self.PanelSize  = array(self.GetClientSizeTuple(),Int32)
 | |
|         self.HalfPanelSize = self.PanelSize / 2 # lrk: added for speed in WorldToPixel
 | |
|         if self.PanelSize[0] == 0 or self.PanelSize[1] == 0:
 | |
|             self.AspectRatio = 1.0
 | |
|         else:
 | |
|             self.AspectRatio = float(self.PanelSize[0]) / self.PanelSize[1]
 | |
|         self.MakeNewBuffers()
 | |
|         self.Draw()
 | |
|         
 | |
|     def OnPaint(self, event):
 | |
|         dc = wx.PaintDC(self)
 | |
|         if self._ForegroundBuffer:
 | |
|             dc.DrawBitmap(self._ForegroundBuffer,0,0)
 | |
|         else:
 | |
|             dc.DrawBitmap(self._Buffer,0,0)
 | |
| 
 | |
|     def Draw(self, Force=False):
 | |
|         """
 | |
|         There is a main buffer set up to double buffer the screen, so
 | |
|         you can get quick re-draws when the window gets uncovered.
 | |
| 
 | |
|         If there are any objects in self._ForeDrawList, then the
 | |
|         background gets drawn to a new buffer, and the foreground
 | |
|         objects get drawn on top of it. The final result if blitted to
 | |
|         the screen, and stored for future Paint events.  This is done so
 | |
|         that you can have a complicated background, but have something
 | |
|         changing on the foreground, without having to wait for the
 | |
|         background to get re-drawn. This can be used to support simple
 | |
|         animation, for instance.
 | |
|         
 | |
|         """
 | |
|         #print "In Draw"
 | |
|         if self.PanelSize < (1,1): 
 | |
|             return
 | |
|         if self.Debug: start = clock()
 | |
|         ScreenDC =  wx.ClientDC(self)
 | |
|         ViewPortWorld = ( self.PixelToWorld((0,0)),
 | |
|                           self.PixelToWorld(self.PanelSize) )
 | |
|         ViewPortBB = array( ( minimum.reduce(ViewPortWorld),
 | |
|                               maximum.reduce(ViewPortWorld) ) )
 | |
|         dc = wx.MemoryDC()
 | |
|         dc.SelectObject(self._Buffer)
 | |
|         if self._BackgroundDirty or Force:
 | |
|             #print "Background is Dirty"
 | |
|             dc.SetBackground(self.BackgroundBrush)
 | |
|             dc.Clear()
 | |
|             if self._HTdc:
 | |
|                 self._HTdc.Clear()
 | |
|             self._DrawObjects(dc, self._DrawList, ScreenDC, ViewPortBB, self._HTdc)
 | |
|             self._BackgroundDirty = False
 | |
| 
 | |
|         if self._ForeDrawList:
 | |
|             ## If an object was just added to the Foreground, there might not yet be a buffer
 | |
|             if self._ForegroundBuffer is None:
 | |
|                 self._ForegroundBuffer = wx.EmptyBitmap(self.PanelSize[0],
 | |
|                                                         self.PanelSize[1])
 | |
| 
 | |
|             dc = wx.MemoryDC() ## I got some strange errors (linewidths wrong) if I didn't make a new DC here
 | |
|             dc.SelectObject(self._ForegroundBuffer)
 | |
|             dc.DrawBitmap(self._Buffer,0,0)
 | |
|             if self._ForegroundHTdc is None:
 | |
|                 self._ForegroundHTdc = wx.MemoryDC()
 | |
|                 self._ForegroundHTdc.SelectObject( wx.EmptyBitmap(
 | |
|                                                    self.PanelSize[0],
 | |
|                                                    self.PanelSize[1]) )
 | |
|             if self._HTdc:
 | |
|                 ## blit the background HT buffer to the foreground HT buffer
 | |
|                 self._ForegroundHTdc.Blit(0, 0,
 | |
|                                           self.PanelSize[0], self.PanelSize[1],
 | |
|                                           self._HTdc, 0, 0)
 | |
|             self._DrawObjects(dc,
 | |
|                               self._ForeDrawList,
 | |
|                               ScreenDC,
 | |
|                               ViewPortBB,
 | |
|                               self._ForegroundHTdc)
 | |
|         ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)
 | |
|         # 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:
 | |
|             ScreenDC.SetPen(wx.Pen('WHITE', 2,wx.SHORT_DASH))
 | |
|             ScreenDC.SetBrush(wx.TRANSPARENT_BRUSH)
 | |
|             ScreenDC.SetLogicalFunction(wx.XOR)
 | |
|             ScreenDC.DrawRectanglePointSize(*self.PrevRBBox)
 | |
|         if self.Debug: print "Drawing took %f seconds of CPU time"%(clock()-start)
 | |
| 
 | |
|         ## Clear the font cache
 | |
|         ## IF you don't do this, the X font server starts to take up Massive amounts of memory
 | |
|         ## This is mostly a problem with very large fonts, that you get with scaled text when zoomed in.
 | |
|         DrawObject.FontList = {}
 | |
| 
 | |
|     def _ShouldRedraw(DrawList, ViewPortBB): # lrk: adapted code from BBCheck
 | |
|         # lrk: Returns the objects that should be redrawn
 | |
| 
 | |
|         BB2 = ViewPortBB
 | |
|         redrawlist = []
 | |
|         for Object in DrawList:
 | |
|             BB1 = Object.BoundingBox
 | |
|             if (BB1[1,0] > BB2[0,0] and BB1[0,0] < BB2[1,0] and
 | |
|                  BB1[1,1] > BB2[0,1] and BB1[0,1] < BB2[1,1]):
 | |
|                 redrawlist.append(Object)
 | |
|         return redrawlist       
 | |
|     _ShouldRedraw = staticmethod(_ShouldRedraw)
 | |
| 
 | |
| 
 | |
| ##    def BBCheck(self, BB1, BB2):
 | |
| ##        """
 | |
| 
 | |
| ##        BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise
 | |
| 
 | |
| ##        """
 | |
| ##        if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and
 | |
| ##             (BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ):
 | |
| ##            return True
 | |
| ##        else:
 | |
| ##            return False
 | |
| 
 | |
|     def Move(self,shift,CoordType):
 | |
|         """
 | |
|         move the image in the window.
 | |
| 
 | |
|         shift is an (x,y) tuple, specifying the amount to shift in each direction
 | |
| 
 | |
|         It can be in any of three coordinates: Panel, Pixel, World,
 | |
|         specified by the CoordType parameter
 | |
| 
 | |
|         Panel coordinates means you want to shift the image by some
 | |
|         fraction of the size of the displaed image
 | |
| 
 | |
|         Pixel coordinates means you want to shift the image by some number of pixels
 | |
| 
 | |
|         World coordinates mean you want to shift the image by an amount
 | |
|         in Floating point world coordinates
 | |
| 
 | |
|         """
 | |
|         
 | |
|         shift = array(shift,Float)
 | |
|         if CoordType == 'Panel':# convert from panel coordinates
 | |
|             shift = shift * array((-1,1),Float) *self.PanelSize/self.TransformVector
 | |
|         elif CoordType == 'Pixel': # convert from pixel coordinates
 | |
|             shift = shift/self.TransformVector
 | |
|         elif CoordType == 'World': # No conversion
 | |
|             pass
 | |
|         else:
 | |
|             raise FloatCanvasException('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._BackgroundDirty = True
 | |
|         self.Draw()
 | |
| 
 | |
|     def Zoom(self,factor,center = None):
 | |
|     
 | |
|         """
 | |
|         Zoom(factor, center) changes the amount of zoom of the image by factor.
 | |
|         If factor is greater than one, the image gets larger.
 | |
|         If factor is less than one, the image gets smaller.
 | |
|         
 | |
|         Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming.
 | |
|         If center is not given, the center will stay the same.
 | |
|         
 | |
|         """
 | |
|         self.Scale = self.Scale*factor
 | |
|         if not center is None:
 | |
|             self.ViewPortCenter = array(center,Float)
 | |
|         self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
 | |
|         self.TransformVector = array((self.Scale,-self.Scale),Float) * self.MapProjectionVector
 | |
|         self._BackgroundDirty = True
 | |
|         self.Draw()
 | |
|         
 | |
|     def ZoomToBB(self, NewBB = None, DrawFlag = True):
 | |
| 
 | |
|         """
 | |
| 
 | |
|         Zooms the image to the bounding box given, or to the bounding
 | |
|         box of all the objects on the canvas, if none is given.
 | |
| 
 | |
|         """
 | |
|         
 | |
|         if not  NewBB is None:
 | |
|             BoundingBox = NewBB
 | |
|         else:
 | |
|             if self.BoundingBoxDirty:
 | |
|                 self._ResetBoundingBox()
 | |
|             BoundingBox = self.BoundingBox
 | |
|         if not BoundingBox is None:
 | |
|             self.ViewPortCenter = array(((BoundingBox[0,0]+BoundingBox[1,0])/2,
 | |
|                                          (BoundingBox[0,1]+BoundingBox[1,1])/2 ),Float)
 | |
|             self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
 | |
|             # Compute the new Scale
 | |
|             BoundingBox = BoundingBox * self.MapProjectionVector
 | |
|             try:
 | |
|                 self.Scale = min(abs(self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0])),
 | |
|                                  abs(self.PanelSize[1] / (BoundingBox[1,1]-BoundingBox[0,1])) )*0.95
 | |
|             except ZeroDivisionError: # this will happen if the BB has zero width or height
 | |
|                 try: #width == 0
 | |
|                     self.Scale = (self.PanelSize[0]  / (BoundingBox[1,0]-BoundingBox[0,0]))*0.95
 | |
|                 except ZeroDivisionError:
 | |
|                     try: # height == 0
 | |
|                         self.Scale = (self.PanelSize[1]  / (BoundingBox[1,1]-BoundingBox[0,1]))*0.95
 | |
|                     except ZeroDivisionError: #zero size! (must be a single point)
 | |
|                         self.Scale = 1
 | |
|                      
 | |
|             self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector
 | |
|             if DrawFlag:
 | |
|                 self._BackgroundDirty = True
 | |
|                 self.Draw()
 | |
|         else:
 | |
|             # Reset the shifting and scaling to defaults when there is no BB
 | |
|             self.ViewPortCenter= array( (0,0), Float)
 | |
|             self.MapProjectionVector = array( (1,1), Float) # No Projection to start!
 | |
|             self.TransformVector = array( (1,-1), Float) # default Transformation
 | |
|             self.Scale = 1
 | |
|             
 | |
|     def RemoveObjects(self, Objects):
 | |
|         for Object in Objects:
 | |
|             self.RemoveObject(Object, ResetBB = False)
 | |
|         self.BoundingBoxDirty = True
 | |
|         
 | |
|     def RemoveObject(self, Object, ResetBB = True):
 | |
|         ##fixme: Using the list.remove method is kind of slow
 | |
|         if Object.InForeground:
 | |
|             self._ForeDrawList.remove(Object)
 | |
|         else:
 | |
|             self._DrawList.remove(Object)
 | |
|             self._BackgroundDirty = True
 | |
|         if ResetBB:
 | |
|             self.BoundingBoxDirty = True
 | |
| 
 | |
|     def ClearAll(self, ResetBB = True):
 | |
|         self._DrawList = []
 | |
|         self._ForeDrawList = []
 | |
|         self._BackgroundDirty = True
 | |
|         self.HitColorGenerator = None
 | |
|         self.UseHitTest = False
 | |
|         if ResetBB:
 | |
|             self._ResetBoundingBox()
 | |
|         self.MakeNewBuffers()
 | |
|         self.HitDict = None
 | |
| 
 | |
| ##    No longer called
 | |
| ##    def _AddBoundingBox(self,NewBB):
 | |
| ##        if self.BoundingBox is None:
 | |
| ##            self.BoundingBox = NewBB
 | |
| ##            self.ZoomToBB(NewBB,DrawFlag = False)
 | |
| ##        else:
 | |
| ##            self.BoundingBox = array( ( (min(self.BoundingBox[0,0],NewBB[0,0]),
 | |
| ##                                         min(self.BoundingBox[0,1],NewBB[0,1])),
 | |
| ##                                        (max(self.BoundingBox[1,0],NewBB[1,0]),
 | |
| ##                                         max(self.BoundingBox[1,1],NewBB[1,1]))),
 | |
| ##                                      Float)
 | |
| 
 | |
|     def _getboundingbox(bboxarray): # lrk: added this
 | |
| 
 | |
|         upperleft = minimum.reduce(bboxarray[:,0])
 | |
|         lowerright = maximum.reduce(bboxarray[:,1])
 | |
|         return array((upperleft, lowerright), Float)
 | |
| 
 | |
|     _getboundingbox = staticmethod(_getboundingbox)
 | |
| 
 | |
|     def _ResetBoundingBox(self):
 | |
|         if self._DrawList or self._ForeDrawList:
 | |
|             bboxarray = zeros((len(self._DrawList)+len(self._ForeDrawList), 2, 2),Float) 
 | |
|             i = -1 # just in case _DrawList is empty
 | |
|             for (i, BB) in enumerate(self._DrawList):
 | |
|                 bboxarray[i] = BB.BoundingBox
 | |
|             for (j, BB) in enumerate(self._ForeDrawList):
 | |
|                 bboxarray[i+j+1] = BB.BoundingBox
 | |
|             self.BoundingBox = self._getboundingbox(bboxarray)
 | |
|         else:
 | |
|             self.BoundingBox = None
 | |
|             self.ViewPortCenter= array( (0,0), Float)
 | |
|             self.TransformVector = array( (1,-1), Float)
 | |
|             self.MapProjectionVector = array( (1,1), Float)			
 | |
|             self.Scale = 1        
 | |
|         self.BoundingBoxDirty = False
 | |
| 
 | |
|     def PixelToWorld(self,Points):
 | |
|         """
 | |
|         Converts coordinates from Pixel coordinates to world coordinates.
 | |
|         
 | |
|         Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates.
 | |
|         
 | |
|         """
 | |
|         return  (((asarray(Points,Float) - (self.PanelSize/2))/self.TransformVector) + self.ViewPortCenter)
 | |
|         
 | |
|     def WorldToPixel(self,Coordinates):
 | |
|         """
 | |
|         This function will get passed to the drawing functions of the objects,
 | |
|         to transform from world to pixel coordinates.
 | |
|         Coordinates should be a NX2 array of (x,y) coordinates, or
 | |
|         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')
 | |
| 
 | |
|     def ScaleWorldToPixel(self,Lengths):
 | |
|         """
 | |
|         This function will get passed to the drawing functions of the objects,
 | |
|         to Change a length from world to pixel coordinates.
 | |
|         
 | |
|         Lengths should be a NX2 array of (x,y) coordinates, or
 | |
|         a 2-tuple, or sequence of 2-tuples.
 | |
|         """
 | |
|         return  ( (asarray(Lengths,Float)*self.TransformVector) ).astype('i')
 | |
| 
 | |
|     def ScalePixelToWorld(self,Lengths):
 | |
|         """
 | |
|         This function computes a pair of x.y lengths,
 | |
|         to change then from pixel to world coordinates.
 | |
|         
 | |
|         Lengths should be a NX2 array of (x,y) coordinates, or
 | |
|         a 2-tuple, or sequence of 2-tuples.
 | |
|         """
 | |
| 
 | |
|         return  (asarray(Lengths,Float) / self.TransformVector)
 | |
|     
 | |
|     def AddObject(self,obj):
 | |
|         # put in a reference to the Canvas, so remove and other stuff can work
 | |
|         obj._Canvas = self
 | |
|         if  obj.InForeground:
 | |
|             self._ForeDrawList.append(obj)
 | |
|             self.UseForeground = True
 | |
|         else:
 | |
|             self._DrawList.append(obj)
 | |
|             self._BackgroundDirty = True
 | |
|         self.BoundingBoxDirty = True
 | |
|         return True
 | |
| 
 | |
|     def _DrawObjects(self, dc, DrawList, ScreenDC, ViewPortBB, HTdc = None):
 | |
|         """
 | |
|         This is a convenience function;
 | |
|         This function takes the list of objects and draws them to specified
 | |
|         device context.  
 | |
|         """
 | |
|         dc.SetBackground(self.BackgroundBrush)
 | |
|         dc.BeginDrawing()
 | |
|         #i = 0
 | |
|         PanelSize0, PanelSize1 = self.PanelSize # for speed
 | |
|         WorldToPixel = self.WorldToPixel # for speed
 | |
|         ScaleWorldToPixel = self.ScaleWorldToPixel # for speed
 | |
|         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)
 | |
|         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 _makeFloatCanvasAddMethods(): ## lrk's code for doing this in module __init__
 | |
|     classnames = ["Circle", "Ellipse", "Rectangle", "ScaledText", "Polygon",
 | |
|                "Line", "Text", "PointSet"]
 | |
|     for classname in classnames:
 | |
|         klass = globals()[classname]
 | |
|         def getaddshapemethod(klass=klass):
 | |
|             def addshape(self, *args, **kwargs):
 | |
|                 Object = klass(*args, **kwargs)
 | |
|                 self.AddObject(Object)
 | |
|                 return Object
 | |
|             return addshape
 | |
|         addshapemethod = getaddshapemethod()
 | |
|         methodname = "Add" + classname
 | |
|         setattr(FloatCanvas, methodname, addshapemethod)
 | |
|         docstring = "        Creates %s and adds its reference to the canvas.\n" % classname
 | |
|         docstring += "        Argument protocol same as %s class" % classname
 | |
|         if klass.__doc__:
 | |
|             docstring += ", whose docstring is:\n%s" % klass.__doc__
 | |
|         FloatCanvas.__dict__[methodname].__doc__ = docstring
 | |
| 
 | |
| _makeFloatCanvasAddMethods()    
 | |
| 
 | |
| 
 |