wxWidgets/wxPython/wx/lib/floatcanvas.py
2004-04-26 22:25:44 +00:00

1352 lines
53 KiB
Python

import wx
from Numeric import array,Float,cos,pi,sum,minimum,maximum,Int32
from time import clock, sleep
import types
import os
ID_ZOOM_IN_BUTTON = wx.NewId()
ID_ZOOM_OUT_BUTTON = wx.NewId()
ID_ZOOM_TO_FIT_BUTTON = wx.NewId()
ID_MOVE_MODE_BUTTON = wx.NewId()
ID_TEST_BUTTON = wx.NewId()
ID_ABOUT_MENU = wx.NewId()
ID_EXIT_MENU = wx.NewId()
ID_ZOOM_TO_FIT_MENU = wx.NewId()
ID_DRAWTEST_MENU = wx.NewId()
ID_DRAWMAP_MENU = wx.NewId()
ID_CLEAR_MENU = wx.NewId()
ID_TEST = wx.NewId()
### These are some functions for bitmaps of icons.
import cPickle, zlib
def GetHandData():
return cPickle.loads(zlib.decompress(
'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\
\x01\xc8S\xb6t\x06A(\x1f\x0b\xa0\xa9\x8c\x9e\x1e6\x19\xa0\xa8\x1e\x88\xd4C\
\x97\xd1\x83\xe8\x80 \x9c2zh\xa6\xc1\x11X\n\xab\x8c\x02\x8a\x0cD!\x92\x12\
\x98\x8c\x1e\x8a\x8b\xd1d\x14\xf4\x90%\x90LC\xf6\xbf\x1e\xba\xab\x91%\xd0\
\xdc\x86C\x06\xd9m\xe8!\xaa\x87S\x86\x1a1\xa7\x07\x00v\x0f[\x17' ))
def GetHandBitmap():
return wx.BitmapFromXPMData(GetHandData())
#----------------------------------------------------------------------
def GetPlusData():
return cPickle.loads(zlib.decompress(
'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\
\x01\xc8S\xb6t\x06A(\x1f\x0b RF\x0f\x08\xb0\xc9@D\xe1r\x08\x19\xb8j=l2`\r\
\xe82HF\xe9a\xc8\xe8\xe9A\x9c@\x8a\x0c\x0e\xd3p\xbb\x00\x8f\xab\xe1>\xd5\xd3\
\xc3\x15:P)l!\n\x91\xc2\x1a\xd6`)\xec\xb1\x00\x92\xc2\x11?\xb8e\x88\x8fSt\
\x19=\x00\x82\x16[\xf7' ))
def GetPlusBitmap():
return wx.BitmapFromXPMData(GetPlusData())
#----------------------------------------------------------------------
def GetMinusData():
return cPickle.loads(zlib.decompress(
'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\
\x01\xc8S\xb6t\x06A(\x1f\x0b RF\x0f\x08\xb0\xc9@D\xe1r\x08\x19\xb8j=\xa2e\
\x10\x16@\x99\xc82zz\x10\'\x90"\x83\xc34r\xdc\x86\xf0\xa9\x9e\x1e\xae\xd0\
\x81Ja\x0bQ\x88\x14\xd6\xb0\x06Ka\x8f\x05\x90\x14\x8e\xf8\xc1-C|\x9c\xa2\xcb\
\xe8\x01\x00\xed\x0f[\x87' ))
def GetMinusBitmap():
return wx.BitmapFromXPMData(GetMinusData())
## This is a bunch of stuff for implimenting interactive use: catching
## when objects are clicked on by the mouse, etc. I've made a start, so if
## you are interesed in making that work, let me know, I may have gotten
## it going by then
#### I probably want a full set of events someday:
## #### mouse over, right click, left click mouse up etc, etc.
## ##FLOATCANVAS_EVENT_LEFT_DOWN = wx.NewEventType()
## ##FLOATCANVAS_EVENT_LEFT_UP = wx.NewEventType()
## ##FLOATCANVAS_EVENT_RIGHT_DOWN = wx.NewEventType()
## ##FLOATCANVAS_EVENT_RIGHT_UP = wx.NewEventType()
## ##FLOATCANVAS_EVENT_MOUSE_OVER = wx.NewEventType()
##WXFLOATCANVASEVENT = wx.NewEventType()
##def EVT_WXFLOATCANVASEVENT( window, function ):
## """Your documentation here"""
## window.Connect( -1, -1, WXFLOATCANVASEVENT, function )
##class wxFloatCanvasObjectEvent(wx.PyCommandEvent):
## def __init__(self, WindowID,Object):
## wx.PyCommandEvent.__init__(self, WXFLOATCANVASEVENT, WindowID)
## self.Object = Object
## def Clone( self ):
## self.__class__( self.GetId() )
##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.
## """
## def __init__(self,depth = 16):
## self.r = 0
## self.g = 0
## self.b = 0
## if depth == 16:
## self.step = 8
## elif depth >= 24:
## self.step = 1
## else:
## raise "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 "Too many objects for HitTest"
## return (self.r,self.g,self.b)
class draw_object:
"""
This is the base class for all the objects that can be drawn.
each object has the following properties; (incomplete)
BoundingBox : is of the form: array((min_x,min_y),(max_x,max_y))
Pen
Brush
"""
def __init__(self,Foreground = 0):
self.Foreground = Foreground
self._Canvas = None
# 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 SetBrush(self,FillColor,FillStyle):
if FillColor is None or FillStyle is None:
self.Brush = wx.TRANSPARENT_BRUSH
self.FillStyle = "Transparent"
else:
if not self.BrushList.has_key((FillColor,FillStyle)):
self.BrushList[(FillColor,FillStyle)] = wx.Brush(FillColor,self.FillStyleList[FillStyle])
self.Brush = self.BrushList[(FillColor,FillStyle)]
def SetPen(self,LineColor,LineStyle,LineWidth):
if (LineColor is None) or (LineStyle is None):
self.Pen = wx.TRANSPARENT_PEN
self.LineStyle = 'Transparent'
else:
if not self.PenList.has_key((LineColor,LineStyle,LineWidth)):
self.PenList[(LineColor,LineStyle,LineWidth)] = wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle])
self.Pen = self.PenList[(LineColor,LineStyle,LineWidth)]
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 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]
def PutInBackground(self):
if self._Canvas and self.Foreground:
self._Canvas._TopDrawList.remove(self)
self._Canvas._DrawList.append(self)
self._Canvas._BackgroundDirty = 1
self.Foreground = 0
def PutInForeground(self):
if self._Canvas and (not self.Foreground):
self._Canvas._TopDrawList.append(self)
self._Canvas._DrawList.remove(self)
self._Canvas._BackgroundDirty = 1
self.Foreground = 1
class Polygon(draw_object):
"""
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,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
draw_object.__init__(self,Foreground)
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.FillColor = FillColor
self.FillStyle = FillStyle
self.SetPen(LineColor,LineStyle,LineWidth)
self.SetBrush(FillColor,FillStyle)
def _Draw(self,dc,WorldToPixel,ScaleFunction):
Points = WorldToPixel(self.Points)
dc.SetPen(self.Pen)
dc.SetBrush(self.Brush)
#dc.DrawPolygon(map(lambda x: (x[0],x[1]), Points.tolist()))
dc.DrawPolygon(Points)
class PolygonSet(draw_object):
"""
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,Foreground = 0):
draw_object.__init__(self, Foreground)
##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,ScaleFunction):
Points = WorldToPixel(self.Points)
Points.shape = (-1,4)
dc.DrawLineList(Points,self.Pens)
class Line(draw_object):
"""
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,LineStyle,LineWidth,Foreground = 0):
draw_object.__init__(self, Foreground)
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)
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:
# It looks like this shouldn't be private
self._Canvas.BoundingBoxDirty = 1
def _Draw(self,dc,WorldToPixel,ScaleFunction):
Points = WorldToPixel(self.Points)
dc.SetPen(self.Pen)
#dc.DrawLines(map(lambda x: (x[0],x[1]), Points.tolist()))
dc.DrawLines(Points)
class LineSet(draw_object):
"""
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,Foreground = 0):
draw_object.__init__(self, Foreground)
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,ScaleFunction):
Points = WorldToPixel(self.Points)
Points.shape = (-1,4)
dc.DrawLineList(Points,self.Pens)
class PointSet(draw_object):
"""
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.
"""
def __init__(self,Points,Color,Diameter,Foreground = 0):
draw_object.__init__(self,Foreground)
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.SetPen(Color,"Solid",1)
self.SetBrush(Color,"Solid")
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:
# It looks like this shouldn't be private
self._Canvas.BoundingBoxDirty = 1
def _Draw(self,dc,WorldToPixel,ScaleFunction):
dc.SetPen(self.Pen)
Points = WorldToPixel(self.Points)
if self.Diameter <= 1:
dc.DrawPointList(Points)
elif self.Diameter <= 2:
# 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))
else:
dc.SetBrush(self.Brush)
radius = int(round(self.Diameter/2))
for (x,y) in Points:
dc.DrawEllipse(((x - radius), (y - radius)), (self.Diameter, self.Diameter))
class Dot(draw_object):
"""
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,Foreground = 0):
draw_object.__init__(self,Foreground)
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,ScaleFunction):
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 Rectangle(draw_object):
def __init__(self,x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
draw_object.__init__(self,Foreground)
self.X = x
self.Y = y
self.Width = width
self.Height = height
self.BoundingBox = array(((x,y),(x+width,y+height)),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,ScaleFunction):
(X,Y) = WorldToPixel((self.X,self.Y))
(Width,Height) = ScaleFunction((self.Width,self.Height))
dc.SetPen(self.Pen)
dc.SetBrush(self.Brush)
dc.DrawRectangle((X,Y), (Width,Height))
class Ellipse(draw_object):
def __init__(self,x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
draw_object.__init__(self,Foreground)
self.X = x
self.Y = y
self.Width = width
self.Height = height
self.BoundingBox = array(((x,y),(x+width,y+height)),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,ScaleFunction):
(X,Y) = WorldToPixel((self.X,self.Y))
(Width,Height) = ScaleFunction((self.Width,self.Height))
dc.SetPen(self.Pen)
dc.SetBrush(self.Brush)
dc.DrawEllipse((X,Y), (Width,Height))
class Circle(draw_object):
def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
draw_object.__init__(self,Foreground)
self.X = x
self.Y = y
self.Diameter = Diameter
self.BoundingBox = array(((x-Diameter/2,y-Diameter/2),(x+Diameter/2,y+Diameter/2)),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,ScaleFunction):
(X,Y) = WorldToPixel((self.X,self.Y))
(Diameter,dummy) = ScaleFunction((self.Diameter,self.Diameter))
dc.SetPen(self.Pen)
dc.SetBrush(self.Brush)
dc.DrawEllipse((X-Diameter/2,Y-Diameter/2), (Diameter,Diameter))
class Text(draw_object):
"""
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
I've provided arguments for Family, Style, and Weight of font, but
have not yet implimented them, so all text is: wx.SWISS, wx.NORMAL, wx.NORMAL.
I'd love it if someone would impliment that!
The size is fixed, and does not scale with the drawing.
"""
def __init__(self,String,x,y,Size,ForeGround,BackGround,Family,Style,Weight,Underline,Position,Foreground = 0):
draw_object.__init__(self,Foreground)
self.String = String
self.Size = Size
self.ForeGround = ForeGround
if BackGround is None:
self.BackGround = None
else:
self.BackGround = BackGround
self.Family = Family
self.Style = Style
self.Weight = Weight
self.Underline = Underline
self.Position = Position
# fixme: this should use the passed in parameters!
self.Font = wx.Font(Size, wx.MODERN, wx.NORMAL, wx.NORMAL, Underline, )
self.BoundingBox = array(((x,y),(x,y)),Float)
self.X = x
self.Y = y
self.x_shift = None
self.y_shift = None
def _Draw(self,dc,WorldToPixel,ScaleFunction):
(X,Y) = WorldToPixel((self.X,self.Y))
dc.SetFont(self.Font)
dc.SetTextForeground(self.ForeGround)
if self.BackGround:
dc.SetBackgroundMode(wx.SOLID)
dc.SetTextBackground(self.BackGround)
else:
dc.SetBackgroundMode(wx.TRANSPARENT)
# compute the shift, and adjust the coordinates, if neccesary
# This had to be put in here, becsuse there is no wx.DC during __init__
if self.x_shift is None or self.y_shift is None:
if self.Position == 'tl':
x_shift,y_shift = 0,0
else:
(w,h) = dc.GetTextExtent(self.String)
if self.Position[0] == 't':
y_shift = 0
elif self.Position[0] == 'c':
y_shift = h/2
elif self.Position[0] == 'b':
y_shift = h
else:
##fixme: this should be a real derived exception
raise "Invalid value for Text Object Position Attribute"
if self.Position[1] == 'l':
x_shift = 0
elif self.Position[1] == 'c':
x_shift = w/2
elif self.Position[1] == 'r':
x_shift = w
else:
##fixme: this should be a real derived exception
raise "Invalid value for Text Object Position Attribute"
self.x_shift = x_shift
self.y_shift = y_shift
dc.DrawText(self.String, (X-self.x_shift, Y-self.y_shift))
#---------------------------------------------------------------------------
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
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.
Copyright: Christopher Barker
License: Same as wxPython
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 = 0,
EnclosingFrame = None,
UseToolbar = 1,
UseBackground = 0,
UseHitTest = 0):
wx.Panel.__init__( self, parent, id, wx.DefaultPosition, size)
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('Projectionfun must be either: "FlatEarth", None, or a function that takes the ViewPortCenter and returns a MapProjectionVector')
self.UseBackground = UseBackground
self.UseHitTest = UseHitTest
self.NumBetweenBlits = 40
## you can have a toolbar with buttons for zoom-in, zoom-out and
## move. If you don't use the toolbar, you should provide your
## own way of navigating the canvas
if UseToolbar:
## Create the vertical sizer for the toolbar and Panel
box = wx.BoxSizer(wx.VERTICAL)
box.Add(self.BuildToolbar(), 0, wx.ALL | wx.ALIGN_LEFT | wx.GROW, 4)
self.DrawPanel = wx.Window(self,-1,wx.DefaultPosition,wx.DefaultSize,wx.SUNKEN_BORDER)
box.Add(self.DrawPanel,1,wx.GROW)
box.Fit(self)
self.SetAutoLayout(True)
self.SetSizer(box)
else:
self.DrawPanel = self
self.DrawPanel.BackgroundBrush = wx.Brush(BackgroundColor,wx.SOLID)
self.Debug = Debug
self.EnclosingFrame = EnclosingFrame
wx.EVT_PAINT(self.DrawPanel, self.OnPaint)
wx.EVT_SIZE(self.DrawPanel, self.OnSize)
wx.EVT_LEFT_DOWN(self.DrawPanel, self.LeftButtonEvent)
wx.EVT_LEFT_UP(self.DrawPanel, self.LeftButtonEvent)
wx.EVT_RIGHT_DOWN(self.DrawPanel, self.RightButtonEvent)
wx.EVT_MOTION(self.DrawPanel, self.LeftButtonEvent)
self._DrawList = []
if self.UseBackground:
self._TopDrawList = []
self.BoundingBox = None
self.BoundingBoxDirty = 0
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
self.GUIMode = None
self.StartRBBox = None
self.PrevRBBox = None
self.StartMove = None
self.PrevMoveBox = None
# called just to make sure everything is initialized
if wx.Platform != "__WXMAC__":
self.OnSize(None)
def BuildToolbar(self):
tb = wx.ToolBar(self,-1)
self.ToolBar = tb
tb.SetToolBitmapSize((23,23))
tb.AddTool(ID_ZOOM_IN_BUTTON, GetPlusBitmap(), isToggle=True,shortHelpString = "Zoom In")
wx.EVT_TOOL(self, ID_ZOOM_IN_BUTTON, self.SetMode)
tb.AddTool(ID_ZOOM_OUT_BUTTON, GetMinusBitmap(), isToggle=True,shortHelpString = "Zoom Out")
wx.EVT_TOOL(self, ID_ZOOM_OUT_BUTTON, self.SetMode)
tb.AddTool(ID_MOVE_MODE_BUTTON, GetHandBitmap(), isToggle=True,shortHelpString = "Move")
wx.EVT_TOOL(self, ID_MOVE_MODE_BUTTON, self.SetMode)
tb.AddSeparator()
tb.AddControl(wx.Button(tb, ID_ZOOM_TO_FIT_BUTTON, "Zoom To Fit",wx.DefaultPosition, wx.DefaultSize))
wx.EVT_BUTTON(self, ID_ZOOM_TO_FIT_BUTTON, self.ZoomToFit)
tb.Realize()
return tb
def SetMode(self,event):
for id in [ID_ZOOM_IN_BUTTON,ID_ZOOM_OUT_BUTTON,ID_MOVE_MODE_BUTTON]:
self.ToolBar.ToggleTool(id,0)
self.ToolBar.ToggleTool(event.GetId(),1)
if event.GetId() == ID_ZOOM_IN_BUTTON:
self.SetGUIMode("ZoomIn")
elif event.GetId() == ID_ZOOM_OUT_BUTTON:
self.SetGUIMode("ZoomOut")
elif event.GetId() == ID_MOVE_MODE_BUTTON:
self.SetGUIMode("Move")
def SetGUIMode(self,Mode):
if Mode in ["ZoomIn","ZoomOut","Move",None]:
self.GUIMode = Mode
else:
raise "Not a valid Mode"
def FlatEarthProjection(self,CenterPoint):
return array((cos(pi*CenterPoint[1]/180),1),Float)
def LeftButtonEvent(self,event):
if self.EnclosingFrame:
if event.Moving:
position = self.PixelToWorld((event.GetX(),event.GetY()))
self.EnclosingFrame.SetStatusText("%8.3f, %8.3f"%tuple(position))
if self.GUIMode:
if self.GUIMode == "ZoomIn":
if event.LeftDown():
self.StartRBBox = (event.GetX(),event.GetY())
self.PrevRBBox = None
elif event.Dragging() and event.LeftIsDown() and self.StartRBBox:
x0,y0 = self.StartRBBox
x1,y1 = event.GetX(),event.GetY()
w, h = abs(x1-x0),abs(y1-y0)
w = max(w,int(h*self.AspectRatio))
h = int(w/self.AspectRatio)
x_c, y_c = (x0+x1)/2 , (y0+y1)/2
dc = wx.ClientDC(self.DrawPanel)
dc.BeginDrawing()
dc.SetPen(wx.Pen('WHITE', 2,wx.SHORT_DASH))
dc.SetBrush(wx.TRANSPARENT_BRUSH)
dc.SetLogicalFunction(wx.XOR)
if self.PrevRBBox:
dc.DrawRectangleXY(*self.PrevRBBox)
dc.DrawRectangleXY(x_c-w/2,y_c-h/2,w,h)
self.PrevRBBox = (x_c-w/2,y_c-h/2,w,h)
dc.EndDrawing()
elif event.LeftUp() and self.StartRBBox :
self.PrevRBBox = None
EndRBBox = (event.GetX(),event.GetY())
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
if self.GUIMode == "ZoomOut":
if event.LeftDown():
Center = self.PixelToWorld((event.GetX(),event.GetY()))
self.Zoom(1/1.5,Center)
elif self.GUIMode == "Move":
if event.LeftDown():
self.StartMove = array((event.GetX(),event.GetY()))
self.PrevMoveBox = None
elif event.Dragging() and event.LeftIsDown() and self.StartMove:
x_1,y_1 = event.GetX(),event.GetY()
w, h = self.PanelSize
x_tl, y_tl = x_1 - self.StartMove[0], y_1 - self.StartMove[1]
dc = wx.ClientDC(self.DrawPanel)
dc.BeginDrawing()
dc.SetPen(wx.Pen('WHITE', 1,))
dc.SetBrush(wx.TRANSPARENT_BRUSH)
dc.SetLogicalFunction(wx.XOR)
if self.PrevMoveBox:
dc.DrawRectangleXY(*self.PrevMoveBox)
dc.DrawRectangleXY(x_tl,y_tl,w,h)
self.PrevMoveBox = (x_tl,y_tl,w,h)
dc.EndDrawing()
elif event.LeftUp() and self.StartMove:
self.PrevMoveBox = None
StartMove = self.StartMove
EndMove = array((event.GetX(),event.GetY()))
if sum((StartMove-EndMove)**2) > 16:
self.Move(StartMove-EndMove,'Pixel')
self.StartMove = None
def RightButtonEvent(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)
else:
event.Skip()
event.Skip()
def MakeNewBuffers(self):
# Make new offscreen bitmap:
self._Buffer = wx.EmptyBitmap(self.PanelSize[0],self.PanelSize[1])
if self.UseBackground:
self._BackBuffer = wx.EmptyBitmap(self.PanelSize[0],self.PanelSize[1])
self._BackgroundDirty = 1
else:
pass
def OnSize(self,event):
self.PanelSize = array(self.DrawPanel.GetClientSizeTuple(),Int32)
try:
self.AspectRatio = self.PanelSize[0]/self.PanelSize[1]
except ZeroDivisionError:
self.AspectRatio = 1.0
self.MakeNewBuffers()
self.Draw()
def OnPaint(self, event):
#dc = wx.BufferedPaintDC(self.DrawPanel, self._Buffer)
dc = wx.PaintDC(self.DrawPanel)
dc.DrawBitmap(self._Buffer, (0,0))
def Draw(self):
"""
The Draw method gets pretty complicated because of all the buffers
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 self.UseBackground is set, and an object is set up with the
"ForeGround" flag, then it gets drawn to the screen after blitting
the background. 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.
"""
if self.Debug: start = clock()
ScreenDC = wx.ClientDC(self.DrawPanel)
ViewPortWorld = ( self.PixelToWorld((0,0)), self.PixelToWorld(self.PanelSize) )
ViewPortBB = array( ( minimum.reduce(ViewPortWorld), maximum.reduce(ViewPortWorld) ) )
if self.UseBackground:
dc = wx.MemoryDC()
dc.SelectObject(self._BackBuffer)
dc.SetBackground(self.DrawPanel.BackgroundBrush)
if self._DrawList:
if self._BackgroundDirty:
dc.BeginDrawing()
dc.Clear()
i = 0
for Object in self._DrawList:
if self.BBCheck(Object.BoundingBox,ViewPortBB):
#print "object is in Bounding Box"
i+=1
Object._Draw(dc,self.WorldToPixel,self.ScaleFunction)
if i % self.NumBetweenBlits == 0:
ScreenDC.Blit((0, 0), self.PanelSize, dc, (0, 0))
dc.EndDrawing()
else:
dc.Clear()
self._BackgroundDirty = 0
dc.SelectObject(self._Buffer)
dc.BeginDrawing()
##Draw Background on Main Buffer:
dc.DrawBitmap(self._BackBuffer,0,0)
#Draw the OnTop stuff
i = 0
for Object in self._TopDrawList:
i+=1
Object._Draw(dc,self.WorldToPixel,self.ScaleFunction)
if i % self.NumBetweenBlits == 0:
ScreenDC.Blit((0, 0), self.PanelSize, dc, (0, 0))
dc.EndDrawing()
else: # not using a Background DC
dc = wx.MemoryDC()
dc.SelectObject(self._Buffer)
dc.SetBackground(self.DrawPanel.BackgroundBrush)
if self._DrawList:
dc.BeginDrawing()
dc.Clear()
i = 0
for Object in self._DrawList:
if self.BBCheck(Object.BoundingBox,ViewPortBB):
#print "object is in Bounding Box"
i+=1
Object._Draw(dc,self.WorldToPixel,self.ScaleFunction)
if i % self.NumBetweenBlits == 0:
ScreenDC.Blit((0, 0), self.PanelSize, dc, (0, 0))
dc.EndDrawing()
else:
dc.Clear()
# now refresh the screen
#ScreenDC.DrawBitmap(self._Buffer,0,0) #NOTE: uisng DrawBitmap didn't work right on MSW
ScreenDC.Blit((0, 0), self.PanelSize, dc, (0, 0))
# If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn
if self.PrevRBBox:
ScreenDC.SetPen(wx.Pen('WHITE', 2,wx.SHORT_DASH))
ScreenDC.SetBrush(wx.TRANSPARENT_BRUSH)
ScreenDC.SetLogicalFunction(wx.XOR)
ScreenDC.DrawRectangleXY(*self.PrevRBBox)
elif self.PrevMoveBox:
ScreenDC.SetPen(wx.Pen('WHITE', 1,))
ScreenDC.SetBrush(wx.TRANSPARENT_BRUSH)
ScreenDC.SetLogicalFunction(wx.XOR)
ScreenDC.DrawRectangleXY(*self.PrevMoveBox)
if self.Debug: print "Drawing took %f seconds of CPU time"%(clock()-start)
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 meand 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 '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 = 1
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 center:
self.ViewPortCenter = array(center,Float)
self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector
self._BackgroundDirty = 1
self.Draw()
def ZoomToFit(self,event):
self.ZoomToBB()
def ZoomToBB(self,NewBB = None,DrawFlag = 1):
"""
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 NewBB:
BoundingBox = NewBB
else:
if self.BoundingBoxDirty:
self._ResetBoundingBox()
BoundingBox = self.BoundingBox
if BoundingBox:
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((self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0])),
(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
self.Scale = (self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0]))*0.95
except ZeroDivisionError:
try: # height
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 = 1
self.Draw()
def RemoveObjects(self,Objects):
for Object in Objects:
self.RemoveObject(Object,ResetBB = 0)
self.BoundingBoxDirty = 1
def RemoveObject(self,Object,ResetBB = 1):
if Object.Foreground:
self._TopDrawList.remove(Object)
else:
self._DrawList.remove(Object)
self._BackgroundDirty = 1
if ResetBB:
self.BoundingBoxDirty = 1
def Clear(self, ResetBB = True):
self._DrawList = []
if self.UseBackground:
self._TopDrawList = []
self._BackgroundDirty = 1
if ResetBB:
self._ResetBoundingBox()
def _AddBoundingBox(self,NewBB):
if self.BoundingBox is None:
self.BoundingBox = NewBB
self.ZoomToBB(NewBB,DrawFlag = 0)
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 _ResetBoundingBox(self):
# NOTE: could you remove an item without recomputing the entire bounding box?
self.BoundingBox = None
if self._DrawList:
self.BoundingBox = self._DrawList[0].BoundingBox
for Object in self._DrawList[1:]:
self._AddBoundingBox(Object.BoundingBox)
if self.UseBackground:
for Object in self._TopDrawList:
self._AddBoundingBox(Object.BoundingBox)
if self.BoundingBox is None:
self.ViewPortCenter= array( (0,0), Float)
self.TransformVector = array( (1,-1), Float)
self.MapProjectionVector = array( (1,1), Float)
self.Scale = 1
self.BoundingBoxDirty = 0
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 (((array(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.
"""
return (((array(Coordinates,Float) - self.ViewPortCenter)*self.TransformVector)+(self.PanelSize/2)).astype('i')
def ScaleFunction(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 (array(Lengths,Float)*self.TransformVector).astype('i')
## This is a set of methods that add objects to the Canvas. It kind
## of seems like a lot of duplication, but I wanted to be able to
## instantiate the draw objects separatley form adding them, but
## also to be able to do add one oin one step. I'm open to better
## ideas...
def AddRectangle(self,x,y,width,height,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
FillColor = None,
FillStyle = "Solid",
Foreground = 0):
Object = Rectangle(x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
self.AddObject(Object)
return Object
def AddEllipse(self,x,y,width,height,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
FillColor = None,
FillStyle = "Solid",
Foreground = 0):
Object = Ellipse(x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
self.AddObject(Object)
return Object
def AddCircle(self,x,y,Diameter,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
FillColor = None,
FillStyle = "Solid",
Foreground = 0):
Object = Circle(x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
self.AddObject(Object)
return Object
def AddDot(self,x,y,Diameter,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
FillColor = None,
FillStyle = "Solid",
Foreground = 0):
Object = Dot(x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
self.AddObject(Object)
return Object
def AddPolygon(self,Points,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
FillColor = None,
FillStyle = "Solid",
Foreground = 0):
Object = Polygon(Points,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
self.AddObject(Object)
return Object
def AddLine(self,Points,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
Foreground = 0):
Object = Line(Points,LineColor,LineStyle,LineWidth,Foreground)
self.AddObject(Object)
return Object
def AddLineSet(self,Points,
LineColors = "Black",
LineStyles = "Solid",
LineWidths = 1,
Foreground = 0):
Object = LineSet(Points,LineColors,LineStyles,LineWidths,Foreground)
self.AddObject(Object)
return Object
def AddPointSet(self,Points,
Color = "Black",
Diameter = 1,
Foreground = 0):
Object = PointSet(Points,Color,Diameter,Foreground)
self.AddObject(Object)
return Object
def AddText(self,String,x,y,
Size = 20,
ForeGround = 'Black',
BackGround = None,
Family = 'Swiss',
Style = 'Normal',
Weight = 'Normal',
Underline = 0,
Position = 'tl',
Foreground = 0):
Object = Text(String,x,y,Size,ForeGround,BackGround,Family,Style,Weight,Underline,Position,Foreground)
self.AddObject(Object)
return Object
def AddObject(self,obj):
# put in a reference to the Canvas, so remove and other stuff can work
obj._Canvas = self
if obj.Foreground and self.UseBackground:
self._TopDrawList.append(obj)
else:
self._DrawList.append(obj)
self._backgrounddirty = 1
self._AddBoundingBox(obj.BoundingBox)
return None