1 from __future__
import division
4 from Numeric
import array
,asarray
,Float
,cos
, sin
, pi
,sum,minimum
,maximum
,Int32
,zeros
, ones
, concatenate
, sqrt
, argmin
, power
, absolute
, matrixmultiply
, transpose
, sometrue
, arange
, hypot
7 from numarray
import array
, asarray
, Float
, cos
, sin
, pi
, sum, minimum
, maximum
, Int32
, zeros
, concatenate
, matrixmultiply
, transpose
, sometrue
, arange
, hypot
9 raise ImportError("I could not import either Numeric or numarray")
11 from time
import clock
, sleep
13 import Resources
# A file with icons, etc for FloatCanvas
20 ## A global variable to hold the Pixels per inch that wxWindows thinks is in use
21 ## This is used for scaling fonts.
22 ## This can't be computed on module __init__, because a wx.App might not have initialized yet.
25 ## a custom Exceptions:
27 class FloatCanvasError(Exception):
30 ## Create all the mouse events
31 # I don't see a need for these two, but maybe some day!
32 #EVT_FC_ENTER_WINDOW = wx.NewEventType()
33 #EVT_FC_LEAVE_WINDOW = wx.NewEventType()
34 EVT_FC_LEFT_DOWN
= wx
.NewEventType()
35 EVT_FC_LEFT_UP
= wx
.NewEventType()
36 EVT_FC_LEFT_DCLICK
= wx
.NewEventType()
37 EVT_FC_MIDDLE_DOWN
= wx
.NewEventType()
38 EVT_FC_MIDDLE_UP
= wx
.NewEventType()
39 EVT_FC_MIDDLE_DCLICK
= wx
.NewEventType()
40 EVT_FC_RIGHT_DOWN
= wx
.NewEventType()
41 EVT_FC_RIGHT_UP
= wx
.NewEventType()
42 EVT_FC_RIGHT_DCLICK
= wx
.NewEventType()
43 EVT_FC_MOTION
= wx
.NewEventType()
44 EVT_FC_MOUSEWHEEL
= wx
.NewEventType()
45 ## these two are for the hit-test stuff, I never make them real Events
46 EVT_FC_ENTER_OBJECT
= wx
.NewEventType()
47 EVT_FC_LEAVE_OBJECT
= wx
.NewEventType()
49 ##Create all mouse event binding functions
50 #def EVT_ENTER_WINDOW( window, function ):
51 # window.Connect( -1, -1, EVT_FC_ENTER_WINDOW, function )
52 #def EVT_LEAVE_WINDOW( window, function ):
53 # window.Connect( -1, -1,EVT_FC_LEAVE_WINDOW , function )
54 def EVT_LEFT_DOWN( window
, function
):
55 window
.Connect( -1, -1,EVT_FC_LEFT_DOWN
, function
)
56 def EVT_LEFT_UP( window
, function
):
57 window
.Connect( -1, -1,EVT_FC_LEFT_UP
, function
)
58 def EVT_LEFT_DCLICK ( window
, function
):
59 window
.Connect( -1, -1,EVT_FC_LEFT_DCLICK
, function
)
60 def EVT_MIDDLE_DOWN ( window
, function
):
61 window
.Connect( -1, -1,EVT_FC_MIDDLE_DOWN
, function
)
62 def EVT_MIDDLE_UP ( window
, function
):
63 window
.Connect( -1, -1,EVT_FC_MIDDLE_UP
, function
)
64 def EVT_MIDDLE_DCLICK ( window
, function
):
65 window
.Connect( -1, -1,EVT_FC_MIDDLE_DCLICK
, function
)
66 def EVT_RIGHT_DOWN ( window
, function
):
67 window
.Connect( -1, -1,EVT_FC_RIGHT_DOWN
, function
)
68 def EVT_RIGHT_UP( window
, function
):
69 window
.Connect( -1, -1,EVT_FC_RIGHT_UP
, function
)
70 def EVT_RIGHT_DCLICK( window
, function
):
71 window
.Connect( -1, -1,EVT_FC_RIGHT_DCLICK
, function
)
72 def EVT_MOTION( window
, function
):
73 window
.Connect( -1, -1,EVT_FC_MOTION
, function
)
74 def EVT_MOUSEWHEEL( window
, function
):
75 window
.Connect( -1, -1,EVT_FC_MOUSEWHEEL
, function
)
77 class _MouseEvent(wx
.PyCommandEvent
):
81 This event class takes a regular wxWindows mouse event as a parameter,
82 and wraps it so that there is access to all the original methods. This
83 is similar to subclassing, but you can't subclass a wxWindows event
85 The goal is to be able to it just like a regular mouse event.
89 GetCoords() , which returns and (x,y) tuple in world coordinates.
91 Another difference is that it is a CommandEvent, which propagates up
92 the window hierarchy until it is handled.
96 def __init__(self
, EventType
, NativeEvent
, WinID
, Coords
= None):
97 wx
.PyCommandEvent
.__init
__(self
)
99 self
.SetEventType( EventType
)
100 self
._NativeEvent
= NativeEvent
103 # I don't think this is used.
104 # def SetCoords(self,Coords):
105 # self.Coords = Coords
110 def __getattr__(self
, name
):
111 #return eval(self.NativeEvent.__getattr__(name) )
112 return getattr(self
._NativeEvent
, name
)
114 def _cycleidxs(indexcount
, maxvalue
, step
):
117 Utility function used by _colorGenerator
123 for idx
in xrange(0, maxvalue
, step
):
124 for tail
in _cycleidxs(indexcount
- 1, maxvalue
, step
):
127 def _colorGenerator():
131 Generates a seris of unique colors used to do hit-tests with the HIt
136 if sys
.platform
== 'darwin':
139 b
= wx
.EmptyBitmap(1,1)
146 raise "ColorGenerator does not work with depth = %s" % depth
147 return _cycleidxs(indexcount
=3, maxvalue
=256, step
=step
)
150 #### I don't know if the Set objects are useful, beyond the pointset
151 #### object. The problem is that when zoomed in, the BB is checked to see
152 #### whether to draw the object. A Set object can defeat this. One day
153 #### I plan to write some custon C++ code to draw sets of objects
155 ##class ObjectSetMixin:
157 ## A mix-in class for draw objects that are sets of objects
159 ## It contains methods for setting lists of pens and brushes
162 ## def SetPens(self,LineColors,LineStyles,LineWidths):
164 ## This method used when an object could have a list of pens, rather than just one
165 ## It is used for LineSet, and perhaps others in the future.
167 ## fixme: this should be in a mixin
169 ## fixme: this is really kludgy, there has got to be a better way!
174 ## if type(LineColors) == types.ListType:
175 ## length = len(LineColors)
177 ## LineColors = [LineColors]
179 ## if type(LineStyles) == types.ListType:
180 ## length = len(LineStyles)
182 ## LineStyles = [LineStyles]
184 ## if type(LineWidths) == types.ListType:
185 ## length = len(LineWidths)
187 ## LineWidths = [LineWidths]
190 ## if len(LineColors) == 1:
191 ## LineColors = LineColors*length
192 ## if len(LineStyles) == 1:
193 ## LineStyles = LineStyles*length
194 ## if len(LineWidths) == 1:
195 ## LineWidths = LineWidths*length
198 ## for (LineColor,LineStyle,LineWidth) in zip(LineColors,LineStyles,LineWidths):
199 ## if LineColor is None or LineStyle is None:
200 ## self.Pens.append(wx.TRANSPARENT_PEN)
201 ## # what's this for?> self.LineStyle = 'Transparent'
202 ## if not self.PenList.has_key((LineColor,LineStyle,LineWidth)):
203 ## Pen = wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle])
204 ## self.Pens.append(Pen)
206 ## self.Pens.append(self.PenList[(LineColor,LineStyle,LineWidth)])
208 ## self.Pens = self.Pens[0]
212 This is the base class for all the objects that can be drawn.
214 One must subclass from this (and an assortment of Mixins) to create
219 def __init__(self
, InForeground
= False, IsVisible
= True):
220 self
.InForeground
= InForeground
225 self
.CallBackFuncs
= {}
227 ## these are the defaults
231 self
.MinHitLineWidth
= 3
232 self
.HitLineWidth
= 3 ## this gets re-set by the subclasses if necessary
237 self
.FillStyle
= "Solid"
239 self
.Visible
= IsVisible
241 # I pre-define all these as class variables to provide an easier
242 # interface, and perhaps speed things up by caching all the Pens
243 # and Brushes, although that may not help, as I think wx now
244 # does that on it's own. Send me a note if you know!
247 ( None,"Transparent") : wx
.TRANSPARENT_BRUSH
,
248 ("Blue","Solid") : wx
.BLUE_BRUSH
,
249 ("Green","Solid") : wx
.GREEN_BRUSH
,
250 ("White","Solid") : wx
.WHITE_BRUSH
,
251 ("Black","Solid") : wx
.BLACK_BRUSH
,
252 ("Grey","Solid") : wx
.GREY_BRUSH
,
253 ("MediumGrey","Solid") : wx
.MEDIUM_GREY_BRUSH
,
254 ("LightGrey","Solid") : wx
.LIGHT_GREY_BRUSH
,
255 ("Cyan","Solid") : wx
.CYAN_BRUSH
,
256 ("Red","Solid") : wx
.RED_BRUSH
259 (None,"Transparent",1) : wx
.TRANSPARENT_PEN
,
260 ("Green","Solid",1) : wx
.GREEN_PEN
,
261 ("White","Solid",1) : wx
.WHITE_PEN
,
262 ("Black","Solid",1) : wx
.BLACK_PEN
,
263 ("Grey","Solid",1) : wx
.GREY_PEN
,
264 ("MediumGrey","Solid",1) : wx
.MEDIUM_GREY_PEN
,
265 ("LightGrey","Solid",1) : wx
.LIGHT_GREY_PEN
,
266 ("Cyan","Solid",1) : wx
.CYAN_PEN
,
267 ("Red","Solid",1) : wx
.RED_PEN
271 "Transparent" : wx
.TRANSPARENT
,
273 "BiDiagonalHatch": wx
.BDIAGONAL_HATCH
,
274 "CrossDiagHatch" : wx
.CROSSDIAG_HATCH
,
275 "FDiagonal_Hatch": wx
.FDIAGONAL_HATCH
,
276 "CrossHatch" : wx
.CROSS_HATCH
,
277 "HorizontalHatch": wx
.HORIZONTAL_HATCH
,
278 "VerticalHatch" : wx
.VERTICAL_HATCH
283 "Transparent": wx
.TRANSPARENT
,
285 "LongDash" : wx
.LONG_DASH
,
286 "ShortDash" : wx
.SHORT_DASH
,
287 "DotDash" : wx
.DOT_DASH
,
290 def Bind(self
, Event
, CallBackFun
):
291 self
.CallBackFuncs
[Event
] = CallBackFun
293 self
._Canvas
.UseHitTest
= True
294 if not self
._Canvas
._HTdc
:
295 self
._Canvas
.MakeNewHTdc()
296 if not self
.HitColor
:
297 if not self
._Canvas
.HitColorGenerator
:
298 self
._Canvas
.HitColorGenerator
= _colorGenerator()
299 self
._Canvas
.HitColorGenerator
.next() # first call to prevent the background color from being used.
300 self
.HitColor
= self
._Canvas
.HitColorGenerator
.next()
301 self
.SetHitPen(self
.HitColor
,self
.HitLineWidth
)
302 self
.SetHitBrush(self
.HitColor
)
303 # put the object in the hit dict, indexed by it's color
304 if not self
._Canvas
.HitDict
:
305 self
._Canvas
.MakeHitDict()
306 self
._Canvas
.HitDict
[Event
][self
.HitColor
] = (self
) # put the object in the hit dict, indexed by it's color
309 ## fixme: this only removes one from each list, there could be more.
310 if self
._Canvas
.HitDict
:
311 for List
in self
._Canvas
.HitDict
.itervalues():
319 def SetBrush(self
,FillColor
,FillStyle
):
320 if FillColor
is None or FillStyle
is None:
321 self
.Brush
= wx
.TRANSPARENT_BRUSH
322 ##fixme: should I really re-set the style?
323 self
.FillStyle
= "Transparent"
325 self
.Brush
= self
.BrushList
.setdefault( (FillColor
,FillStyle
), wx
.Brush(FillColor
,self
.FillStyleList
[FillStyle
] ) )
327 def SetPen(self
,LineColor
,LineStyle
,LineWidth
):
328 if (LineColor
is None) or (LineStyle
is None):
329 self
.Pen
= wx
.TRANSPARENT_PEN
330 self
.LineStyle
= 'Transparent'
332 self
.Pen
= self
.PenList
.setdefault( (LineColor
,LineStyle
,LineWidth
), wx
.Pen(LineColor
,LineWidth
,self
.LineStyleList
[LineStyle
]) )
334 def SetHitBrush(self
,HitColor
):
336 self
.HitBrush
= wx
.TRANSPARENT_BRUSH
338 self
.HitBrush
= self
.BrushList
.setdefault( (HitColor
,"solid"), wx
.Brush(HitColor
,self
.FillStyleList
["Solid"] ) )
340 def SetHitPen(self
,HitColor
,LineWidth
):
342 self
.HitPen
= wx
.TRANSPARENT_PEN
344 self
.HitPen
= self
.PenList
.setdefault( (HitColor
, "solid", self
.HitLineWidth
), wx
.Pen(HitColor
, self
.HitLineWidth
, self
.LineStyleList
["Solid"]) )
346 def PutInBackground(self
):
347 if self
._Canvas
and self
.InForeground
:
348 self
._Canvas
._ForeDrawList
.remove(self
)
349 self
._Canvas
._DrawList
.append(self
)
350 self
._Canvas
._BackgroundDirty
= True
351 self
.InForeground
= False
353 def PutInForeground(self
):
354 if self
._Canvas
and (not self
.InForeground
):
355 self
._Canvas
._ForeDrawList
.append(self
)
356 self
._Canvas
._DrawList
.remove(self
)
357 self
._Canvas
._BackgroundDirty
= True
358 self
.InForeground
= True
366 class ColorOnlyMixin
:
369 Mixin class for objects that have just one color, rather than a fill
374 def SetColor(self
, Color
):
375 self
.SetPen(Color
,"Solid",1)
376 self
.SetBrush(Color
,"Solid")
378 SetFillColor
= SetColor
# Just to provide a consistant interface
383 Mixin class for objects that have just one color, rather than a fill
388 def SetLineColor(self
, LineColor
):
389 self
.LineColor
= LineColor
390 self
.SetPen(LineColor
,self
.LineStyle
,self
.LineWidth
)
392 def SetLineStyle(self
, LineStyle
):
393 self
.LineStyle
= LineStyle
394 self
.SetPen(self
.LineColor
,LineStyle
,self
.LineWidth
)
396 def SetLineWidth(self
, LineWidth
):
397 self
.LineWidth
= LineWidth
398 self
.SetPen(self
.LineColor
,self
.LineStyle
,LineWidth
)
400 class LineAndFillMixin(LineOnlyMixin
):
403 Mixin class for objects that have both a line and a fill color and
407 def SetFillColor(self
, FillColor
):
408 self
.FillColor
= FillColor
409 self
.SetBrush(FillColor
, self
.FillStyle
)
411 def SetFillStyle(self
, FillStyle
):
412 self
.FillStyle
= FillStyle
413 self
.SetBrush(self
.FillColor
,FillStyle
)
418 This is a mixin class that provides some methods suitable for use
419 with objects that have a single (x,y) coordinate pair.
423 def Move(self
, Delta
):
426 Move(Delta): moves the object by delta, where delta is a
427 (dx,dy) pair. Ideally a Numpy array of shape (2,)
431 Delta
= asarray(Delta
, Float
)
433 self
.BoundingBox
= self
.BoundingBox
+ Delta
436 self
._Canvas
.BoundingBoxDirty
= True
438 def CalcBoundingBox(self
):
439 ## This may get overwritten in some subclasses
440 self
.BoundingBox
= array( (self
.XY
, self
.XY
), Float
)
442 def SetPoint(self
, xy
):
443 xy
= array( xy
, Float
)
448 self
.BoundingBox
= self
.BoundingBox
+ Delta
450 #self.CalcBoundingBox()
452 self
._Canvas
.BoundingBoxDirty
= True
454 class PointsObjectMixin
:
457 This is a mixin class that provides some methods suitable for use
458 with objects that have a set of (x,y) coordinate pairs.
463 ## This is code for the PointsObjectMixin object, it needs to be adapted and tested.
464 ## Is the neccesary at all: you can always do:
465 ## Object.SetPoints( Object.Points + delta, copy = False)
466 ## def Move(self, Delta ):
469 ## Move(Delta): moves the object by delta, where delta is an (dx,
470 ## dy) pair. Ideally a Numpy array of shape (2,)
474 ## Delta = array(Delta, Float)
476 ## self.BoundingBox = self.BoundingBox + Delta##array((self.XY, (self.XY + self.WH)), Float)
478 ## self._Canvas.BoundingBoxDirty = True
480 def CalcBoundingBox(self
):
481 self
.BoundingBox
= array(((min(self
.Points
[:,0]),
482 min(self
.Points
[:,1]) ),
483 (max(self
.Points
[:,0]),
484 max(self
.Points
[:,1]) ) ), Float
)
486 self
._Canvas
.BoundingBoxDirty
= True
488 def SetPoints(self
, Points
, copy
= True):
490 Sets the coordinates of the points of the object to Points (NX2 array).
492 By default, a copy is made, if copy is set to False, a reference
493 is used, iff Points is a NumPy array of Floats. This allows you
494 to change some or all of the points without making any copies.
498 Points = Object.Points
499 Points += (5,10) # shifts the points 5 in the x dir, and 10 in the y dir.
500 Object.SetPoints(Points, False) # Sets the points to the same array as it was
504 self
.Points
= array(Points
, Float
)
505 self
.Points
.shape
= (-1,2) # Make sure it is a NX2 array, even if there is only one point
507 self
.Points
= asarray(Points
, Float
)
508 self
.CalcBoundingBox()
511 class Polygon(DrawObject
,PointsObjectMixin
,LineAndFillMixin
):
515 The Polygon class takes a list of 2-tuples, or a NX2 NumPy array of
516 point coordinates. so that Points[N][0] is the x-coordinate of
517 point N and Points[N][1] is the y-coordinate or Points[N,0] is the
518 x-coordinate of point N and Points[N,1] is the y-coordinate for
521 The other parameters specify various properties of the Polygon, and
522 should be self explanatory.
532 InForeground
= False):
533 DrawObject
.__init
__(self
,InForeground
)
534 self
.Points
= array(Points
,Float
) # this DOES need to make a copy
535 self
.CalcBoundingBox()
537 self
.LineColor
= LineColor
538 self
.LineStyle
= LineStyle
539 self
.LineWidth
= LineWidth
540 self
.FillColor
= FillColor
541 self
.FillStyle
= FillStyle
543 self
.HitLineWidth
= max(LineWidth
,self
.MinHitLineWidth
)
545 self
.SetPen(LineColor
,LineStyle
,LineWidth
)
546 self
.SetBrush(FillColor
,FillStyle
)
548 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
= None, HTdc
=None):
549 Points
= WorldToPixel(self
.Points
)#.tolist()
551 dc
.SetBrush(self
.Brush
)
552 dc
.DrawPolygon(Points
)
553 if HTdc
and self
.HitAble
:
554 HTdc
.SetPen(self
.HitPen
)
555 HTdc
.SetBrush(self
.HitBrush
)
556 HTdc
.DrawPolygon(Points
)
558 ##class PolygonSet(DrawObject):
560 ## The PolygonSet class takes a Geometry.Polygon object.
561 ## so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
563 ## it creates a set of line segments, from (x1,y1) to (x2,y2)
567 ## def __init__(self,PolySet,LineColors,LineStyles,LineWidths,FillColors,FillStyles,InForeground = False):
568 ## DrawObject.__init__(self, InForeground)
570 ## ##fixme: there should be some error checking for everything being the right length.
573 ## self.Points = array(Points,Float)
574 ## self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
576 ## self.LineColors = LineColors
577 ## self.LineStyles = LineStyles
578 ## self.LineWidths = LineWidths
579 ## self.FillColors = FillColors
580 ## self.FillStyles = FillStyles
582 ## self.SetPens(LineColors,LineStyles,LineWidths)
584 ## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
585 ## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
586 ## Points = WorldToPixel(self.Points)
587 ## Points.shape = (-1,4)
588 ## dc.DrawLineList(Points,self.Pens)
591 class Line(DrawObject
,PointsObjectMixin
,LineOnlyMixin
):
594 The Line class takes a list of 2-tuples, or a NX2 NumPy Float array
595 of point coordinates.
597 It will draw a straight line if there are two points, and a polyline
598 if there are more than two.
601 def __init__(self
,Points
,
605 InForeground
= False):
606 DrawObject
.__init
__(self
, InForeground
)
609 self
.Points
= array(Points
,Float
)
610 self
.CalcBoundingBox()
612 self
.LineColor
= LineColor
613 self
.LineStyle
= LineStyle
614 self
.LineWidth
= LineWidth
616 self
.SetPen(LineColor
,LineStyle
,LineWidth
)
618 self
.HitLineWidth
= max(LineWidth
,self
.MinHitLineWidth
)
621 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
622 Points
= WorldToPixel(self
.Points
)
625 if HTdc
and self
.HitAble
:
626 HTdc
.SetPen(self
.HitPen
)
627 HTdc
.DrawLines(Points
)
629 class Arrow(DrawObject
,XYObjectMixin
,LineOnlyMixin
):
632 Arrow(XY, # coords of origin of arrow (x,y)
633 Length, # length of arrow in pixels
634 theta, # angle of arrow in degrees: zero is straight up
635 # angle is to the right
641 InForeground = False):
643 It will draw an arrow , starting at the point, (X,Y) pointing in
654 LineWidth
= 2, # pixels
655 ArrowHeadSize
= 8, # pixels
656 ArrowHeadAngle
= 30, # degrees
657 InForeground
= False):
659 DrawObject
.__init
__(self
, InForeground
)
661 self
.XY
= array(XY
, Float
)
662 self
.XY
.shape
= (2,) # Make sure it is a 1X2 array, even if there is only one point
664 self
.Direction
= float(Direction
)
665 self
.ArrowHeadSize
= ArrowHeadSize
666 self
.ArrowHeadAngle
= float(ArrowHeadAngle
)
668 self
.CalcArrowPoints()
669 self
.CalcBoundingBox()
671 self
.LineColor
= LineColor
672 self
.LineStyle
= LineStyle
673 self
.LineWidth
= LineWidth
675 self
.SetPen(LineColor
,LineStyle
,LineWidth
)
677 ##fixme: How should the HitTest be drawn?
678 self
.HitLineWidth
= max(LineWidth
,self
.MinHitLineWidth
)
680 def SetDirection(self
, Direction
):
681 self
.Direction
= float(Direction
)
682 self
.CalcArrowPoints()
684 def SetLength(self
, Length
):
686 self
.CalcArrowPoints()
688 def SetLengthDirection(self
, Length
, Direction
):
689 self
.Direction
= float(Direction
)
691 self
.CalcArrowPoints()
693 def SetLength(self
, Length
):
695 self
.CalcArrowPoints()
697 ## fixme: cache this?
698 def CalcArrowPoints(self
):
700 S
= self
.ArrowHeadSize
701 phi
= self
.ArrowHeadAngle
* pi
/ 360
702 theta
= (self
.Direction
-90.0) * pi
/ 180
703 ArrowPoints
= array( ( (0, L
, L
- S
*cos(phi
),L
, L
- S
*cos(phi
) ),
704 (0, 0, S
*sin(phi
), 0, -S
*sin(phi
) ) ),
706 RotationMatrix
= array( ( ( cos(theta
), -sin(theta
) ),
707 ( sin(theta
), cos(theta
) ) ),
710 ArrowPoints
= matrixmultiply(RotationMatrix
, ArrowPoints
)
711 self
.ArrowPoints
= transpose(ArrowPoints
)
713 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
715 xy
= WorldToPixel(self
.XY
)
716 ArrowPoints
= xy
+ self
.ArrowPoints
717 dc
.DrawLines(ArrowPoints
)
718 if HTdc
and self
.HitAble
:
719 HTdc
.SetPen(self
.HitPen
)
720 HTdc
.DrawLines(ArrowPoints
)
722 ##class LineSet(DrawObject, ObjectSetMixin):
724 ## The LineSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
725 ## so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
727 ## it creates a set of line segments, from (x1,y1) to (x2,y2)
731 ## def __init__(self,Points,LineColors,LineStyles,LineWidths,InForeground = False):
732 ## DrawObject.__init__(self, InForeground)
734 ## NumLines = len(Points) / 2
735 ## ##fixme: there should be some error checking for everything being the right length.
738 ## self.Points = array(Points,Float)
739 ## self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
741 ## self.LineColors = LineColors
742 ## self.LineStyles = LineStyles
743 ## self.LineWidths = LineWidths
745 ## self.SetPens(LineColors,LineStyles,LineWidths)
747 ## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
748 ## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
749 ## Points = WorldToPixel(self.Points)
750 ## Points.shape = (-1,4)
751 ## dc.DrawLineList(Points,self.Pens)
753 class PointSet(DrawObject
,PointsObjectMixin
, ColorOnlyMixin
):
756 The PointSet class takes a list of 2-tuples, or a NX2 NumPy array of
759 If Points is a sequence of tuples: Points[N][0] is the x-coordinate of
760 point N and Points[N][1] is the y-coordinate.
762 If Points is a NumPy array: Points[N,0] is the x-coordinate of point
763 N and Points[N,1] is the y-coordinate for arrays.
765 Each point will be drawn the same color and Diameter. The Diameter
766 is in screen pixels, not world coordinates.
768 The hit-test code does not distingish between the points, you will
769 only know that one of the points got hit, not which one. You can use
770 PointSet.FindClosestPoint(WorldPoint) to find out which one
772 In the case of points, the HitLineWidth is used as diameter.
775 def __init__(self
, Points
, Color
= "Black", Diameter
= 1, InForeground
= False):
776 DrawObject
.__init
__(self
,InForeground
)
778 self
.Points
= array(Points
,Float
)
779 self
.Points
.shape
= (-1,2) # Make sure it is a NX2 array, even if there is only one point
780 self
.CalcBoundingBox()
781 self
.Diameter
= Diameter
783 self
.HitLineWidth
= self
.MinHitLineWidth
786 def SetDiameter(self
,Diameter
):
787 self
.Diameter
= Diameter
789 def FindClosestPoint(self
, XY
):
792 Returns the index of the closest point to the point, XY, given
793 in World coordinates. It's essentially random which you get if
794 there are more than one that are the same.
796 This can be used to figure out which point got hit in a mouse
797 binding callback, for instance. It's a lot faster that using a
798 lot of separate points.
802 return argmin(hypot(d
[:,0],d
[:,1]))
805 def DrawD2(self
, dc
, Points
):
806 # A Little optimization for a diameter2 - point
807 dc
.DrawPointList(Points
)
808 dc
.DrawPointList(Points
+ (1,0))
809 dc
.DrawPointList(Points
+ (0,1))
810 dc
.DrawPointList(Points
+ (1,1))
812 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
814 Points
= WorldToPixel(self
.Points
)
815 if self
.Diameter
<= 1:
816 dc
.DrawPointList(Points
)
817 elif self
.Diameter
<= 2:
818 self
.DrawD2(dc
, Points
)
820 dc
.SetBrush(self
.Brush
)
821 radius
= int(round(self
.Diameter
/2))
822 ##fixme: I really should add a DrawCircleList to wxPython
823 if len(Points
) > 100:
825 xywh
= concatenate((xy
-radius
, ones(xy
.shape
) * self
.Diameter
), 1 )
826 dc
.DrawEllipseList(xywh
)
829 dc
.DrawCircle(xy
[0],xy
[1], radius
)
830 if HTdc
and self
.HitAble
:
831 HTdc
.SetPen(self
.HitPen
)
832 HTdc
.SetBrush(self
.HitBrush
)
833 if self
.Diameter
<= 1:
834 HTdc
.DrawPointList(Points
)
835 elif self
.Diameter
<= 2:
836 self
.DrawD2(HTdc
, Points
)
838 if len(Points
) > 100:
840 xywh
= concatenate((xy
-radius
, ones(xy
.shape
) * self
.Diameter
), 1 )
841 HTdc
.DrawEllipseList(xywh
)
844 HTdc
.DrawCircle(xy
[0],xy
[1], radius
)
846 class Point(DrawObject
,XYObjectMixin
,ColorOnlyMixin
):
849 The Point class takes a 2-tuple, or a (2,) NumPy array of point
852 The Diameter is in screen points, not world coordinates, So the
853 Bounding box is just the point, and doesn't include the Diameter.
855 The HitLineWidth is used as diameter for the
859 def __init__(self
, XY
, Color
= "Black", Diameter
= 1, InForeground
= False):
860 DrawObject
.__init
__(self
, InForeground
)
862 self
.XY
= array(XY
, Float
)
863 self
.XY
.shape
= (2,) # Make sure it is a 1X2 array, even if there is only one point
864 self
.CalcBoundingBox()
866 self
.Diameter
= Diameter
868 self
.HitLineWidth
= self
.MinHitLineWidth
870 def SetDiameter(self
,Diameter
):
871 self
.Diameter
= Diameter
874 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
876 xy
= WorldToPixel(self
.XY
)
877 if self
.Diameter
<= 1:
878 dc
.DrawPoint(xy
[0], xy
[1])
880 dc
.SetBrush(self
.Brush
)
881 radius
= int(round(self
.Diameter
/2))
882 dc
.DrawCircle(xy
[0],xy
[1], radius
)
883 if HTdc
and self
.HitAble
:
884 HTdc
.SetPen(self
.HitPen
)
885 if self
.Diameter
<= 1:
886 HTdc
.DrawPoint(xy
[0], xy
[1])
888 HTdc
.SetBrush(self
.HitBrush
)
889 HTdc
.DrawCircle(xy
[0],xy
[1], radius
)
891 class SquarePoint(DrawObject
,XYObjectMixin
,ColorOnlyMixin
):
894 The SquarePoint class takes a 2-tuple, or a (2,) NumPy array of point
895 coordinates. It produces a square dot, centered on Point
897 The Size is in screen points, not world coordinates, so the
898 Bounding box is just the point, and doesn't include the Size.
900 The HitLineWidth is used as diameter for the
904 def __init__(self
, Point
, Color
= "Black", Size
= 4, InForeground
= False):
905 DrawObject
.__init
__(self
, InForeground
)
907 self
.XY
= array(Point
, Float
)
908 self
.XY
.shape
= (2,) # Make sure it is a 1X2 array, even if there is only one point
909 self
.CalcBoundingBox()
913 self
.HitLineWidth
= self
.MinHitLineWidth
915 def SetSize(self
,Size
):
918 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
921 xc
,yc
= WorldToPixel(self
.XY
)
928 dc
.SetBrush(self
.Brush
)
929 dc
.DrawRectangle(x
, y
, Size
, Size
)
930 if HTdc
and self
.HitAble
:
931 HTdc
.SetPen(self
.HitPen
)
933 HTdc
.DrawPoint(xc
, xc
)
935 HTdc
.SetBrush(self
.HitBrush
)
936 HTdc
.DrawRectangle(x
, y
, Size
, Size
)
938 class RectEllipse(DrawObject
, XYObjectMixin
, LineAndFillMixin
):
939 def __init__(self
, XY
, WH
,
945 InForeground
= False):
947 DrawObject
.__init
__(self
,InForeground
)
949 self
.XY
= array( XY
, Float
)
951 self
.WH
= array( WH
, Float
)
953 self
.BoundingBox
= array((self
.XY
, (self
.XY
+ self
.WH
)), Float
)
954 self
.LineColor
= LineColor
955 self
.LineStyle
= LineStyle
956 self
.LineWidth
= LineWidth
957 self
.FillColor
= FillColor
958 self
.FillStyle
= FillStyle
960 self
.HitLineWidth
= max(LineWidth
,self
.MinHitLineWidth
)
962 self
.SetPen(LineColor
,LineStyle
,LineWidth
)
963 self
.SetBrush(FillColor
,FillStyle
)
965 def SetShape(self
, XY
, WH
):
966 self
.XY
= array( XY
, Float
)
967 self
.WH
= array( WH
, Float
)
968 self
.CalcBoundingBox()
971 def SetUpDraw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
):
973 dc
.SetBrush(self
.Brush
)
974 if HTdc
and self
.HitAble
:
975 HTdc
.SetPen(self
.HitPen
)
976 HTdc
.SetBrush(self
.HitBrush
)
977 return ( WorldToPixel(self
.XY
),
978 ScaleWorldToPixel(self
.WH
) )
980 def CalcBoundingBox(self
):
981 self
.BoundingBox
= array((self
.XY
, (self
.XY
+ self
.WH
) ), Float
)
982 self
._Canvas
.BoundingBoxDirty
= True
985 class Rectangle(RectEllipse
):
987 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
988 ( XY
, WH
) = self
.SetUpDraw(dc
,
992 dc
.DrawRectanglePointSize(XY
, WH
)
993 if HTdc
and self
.HitAble
:
994 HTdc
.DrawRectanglePointSize(XY
, WH
)
996 class Ellipse(RectEllipse
):
998 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
999 ( XY
, WH
) = self
.SetUpDraw(dc
,
1003 dc
.DrawEllipsePointSize(XY
, WH
)
1004 if HTdc
and self
.HitAble
:
1005 HTdc
.DrawEllipsePointSize(XY
, WH
)
1007 class Circle(Ellipse
):
1009 def __init__(self
, XY
, Diameter
, **kwargs
):
1010 self
.Center
= array(XY
, Float
)
1011 Diameter
= float(Diameter
)
1012 RectEllipse
.__init
__(self
,
1013 self
.Center
- Diameter
/2.0,
1014 (Diameter
, Diameter
),
1017 def SetDiameter(self
, Diameter
):
1018 Diameter
= float(Diameter
)
1019 XY
= self
.Center
- (Diameter
/2.0)
1021 (Diameter
, Diameter
)
1024 class TextObjectMixin(XYObjectMixin
):
1027 A mix in class that holds attributes and methods that are needed by
1032 ## I'm caching fonts, because on GTK, getting a new font can take a
1033 ## while. However, it gets cleared after every full draw as hanging
1034 ## on to a bunch of large fonts takes a massive amount of memory.
1038 LayoutFontSize
= 12 # font size used for calculating layout
1040 def SetFont(self
, Size
, Family
, Style
, Weight
, Underline
, FaceName
):
1041 self
.Font
= self
.FontList
.setdefault( (Size
,
1055 def SetColor(self
, Color
):
1058 def SetBackgroundColor(self
, BackgroundColor
):
1059 self
.BackgroundColor
= BackgroundColor
1061 def SetText(self
, String
):
1063 Re-sets the text displayed by the object
1065 In the case of the ScaledTextBox, it will re-do the layout as appropriate
1067 Note: only tested with the ScaledTextBox
1071 self
.String
= String
1074 def LayoutText(self
):
1076 A dummy method to re-do the layout of the text.
1078 A derived object needs to override this if required.
1083 ## store the function that shift the coords for drawing text. The
1084 ## "c" parameter is the correction for world coordinates, rather
1085 ## than pixel coords as the y axis is reversed
1086 ## pad is the extra space around the text
1087 ## if world = 1, the vertical shift is done in y-up coordinates
1088 ShiftFunDict
= {'tl': lambda x
, y
, w
, h
, world
=0, pad
=0: (x
+ pad
, y
+ pad
- 2*world
*pad
),
1089 'tc': lambda x
, y
, w
, h
, world
=0, pad
=0: (x
- w
/2, y
+ pad
- 2*world
*pad
),
1090 'tr': lambda x
, y
, w
, h
, world
=0, pad
=0: (x
- w
- pad
, y
+ pad
- 2*world
*pad
),
1091 'cl': lambda x
, y
, w
, h
, world
=0, pad
=0: (x
+ pad
, y
- h
/2 + world
*h
),
1092 'cc': lambda x
, y
, w
, h
, world
=0, pad
=0: (x
- w
/2, y
- h
/2 + world
*h
),
1093 'cr': lambda x
, y
, w
, h
, world
=0, pad
=0: (x
- w
- pad
, y
- h
/2 + world
*h
),
1094 'bl': lambda x
, y
, w
, h
, world
=0, pad
=0: (x
+ pad
, y
- h
+ 2*world
*h
- pad
+ world
*2*pad
) ,
1095 'bc': lambda x
, y
, w
, h
, world
=0, pad
=0: (x
- w
/2, y
- h
+ 2*world
*h
- pad
+ world
*2*pad
) ,
1096 'br': lambda x
, y
, w
, h
, world
=0, pad
=0: (x
- w
- pad
, y
- h
+ 2*world
*h
- pad
+ world
*2*pad
)}
1098 class Text(DrawObject
, TextObjectMixin
):
1100 This class creates a text object, placed at the coordinates,
1101 x,y. the "Position" argument is a two charactor string, indicating
1102 where in relation to the coordinates the string should be oriented.
1104 The first letter is: t, c, or b, for top, center and bottom The
1105 second letter is: l, c, or r, for left, center and right The
1106 position refers to the position relative to the text itself. It
1107 defaults to "tl" (top left).
1109 Size is the size of the font in pixels, or in points for printing
1110 (if it ever gets implimented). Those will be the same, If you assume
1114 Font family, a generic way of referring to fonts without
1115 specifying actual facename. One of:
1116 wx.DEFAULT: Chooses a default font.
1117 wx.DECORATIVE: A decorative font.
1118 wx.ROMAN: A formal, serif font.
1119 wx.SCRIPT: A handwriting font.
1120 wx.SWISS: A sans-serif font.
1121 wx.MODERN: A fixed pitch font.
1122 NOTE: these are only as good as the wxWindows defaults, which aren't so good.
1124 One of wx.NORMAL, wx.SLANT and wx.ITALIC.
1126 One of wx.NORMAL, wx.LIGHT and wx.BOLD.
1128 The value can be True or False. At present this may have an an
1129 effect on Windows only.
1131 Alternatively, you can set the kw arg: Font, to a wx.Font, and the
1132 above will be ignored.
1134 The size is fixed, and does not scale with the drawing.
1136 The hit-test is done on the entire text extent
1140 def __init__(self
,String
, xy
,
1143 BackgroundColor
= None,
1149 InForeground
= False,
1152 DrawObject
.__init
__(self
,InForeground
)
1154 self
.String
= String
1155 # Input size in in Pixels, compute points size from PPI info.
1156 # fixme: for printing, we'll have to do something a little different
1157 self
.Size
= int(round(72.0 * Size
/ ScreenPPI
))
1160 self
.BackgroundColor
= BackgroundColor
1165 FaceName
= Font
.GetFaceName()
1166 Family
= Font
.GetFamily()
1167 Size
= Font
.GetPointSize()
1168 Style
= Font
.GetStyle()
1169 Underlined
= Font
.GetUnderlined()
1170 Weight
= Font
.GetWeight()
1171 self
.SetFont(Size
, Family
, Style
, Weight
, Underline
, FaceName
)
1173 self
.BoundingBox
= array((xy
, xy
),Float
)
1175 self
.XY
= asarray(xy
)
1176 self
.XY
.shape
= (2,)
1178 (self
.TextWidth
, self
.TextHeight
) = (None, None)
1179 self
.ShiftFun
= self
.ShiftFunDict
[Position
]
1181 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
1182 XY
= WorldToPixel(self
.XY
)
1183 dc
.SetFont(self
.Font
)
1184 dc
.SetTextForeground(self
.Color
)
1185 if self
.BackgroundColor
:
1186 dc
.SetBackgroundMode(wx
.SOLID
)
1187 dc
.SetTextBackground(self
.BackgroundColor
)
1189 dc
.SetBackgroundMode(wx
.TRANSPARENT
)
1190 if self
.TextWidth
is None or self
.TextHeight
is None:
1191 (self
.TextWidth
, self
.TextHeight
) = dc
.GetTextExtent(self
.String
)
1192 XY
= self
.ShiftFun(XY
[0], XY
[1], self
.TextWidth
, self
.TextHeight
)
1193 dc
.DrawTextPoint(self
.String
, XY
)
1194 if HTdc
and self
.HitAble
:
1195 HTdc
.SetPen(self
.HitPen
)
1196 HTdc
.SetBrush(self
.HitBrush
)
1197 HTdc
.DrawRectanglePointSize(XY
, (self
.TextWidth
, self
.TextHeight
) )
1199 class ScaledText(DrawObject
, TextObjectMixin
):
1201 This class creates a text object that is scaled when zoomed. It is
1202 placed at the coordinates, x,y. the "Position" argument is a two
1203 charactor string, indicating where in relation to the coordinates
1204 the string should be oriented.
1206 The first letter is: t, c, or b, for top, center and bottom The
1207 second letter is: l, c, or r, for left, center and right The
1208 position refers to the position relative to the text itself. It
1209 defaults to "tl" (top left).
1211 Size is the size of the font in world coordinates.
1214 Font family, a generic way of referring to fonts without
1215 specifying actual facename. One of:
1216 wx.DEFAULT: Chooses a default font.
1217 wx.DECORATI: A decorative font.
1218 wx.ROMAN: A formal, serif font.
1219 wx.SCRIPT: A handwriting font.
1220 wx.SWISS: A sans-serif font.
1221 wx.MODERN: A fixed pitch font.
1222 NOTE: these are only as good as the wxWindows defaults, which aren't so good.
1224 One of wx.NORMAL, wx.SLANT and wx.ITALIC.
1226 One of wx.NORMAL, wx.LIGHT and wx.BOLD.
1228 The value can be True or False. At present this may have an an
1229 effect on Windows only.
1231 Alternatively, you can set the kw arg: Font, to a wx.Font, and the
1232 above will be ignored. The size of the font you specify will be
1233 ignored, but the rest of its attributes will be preserved.
1235 The size will scale as the drawing is zoomed.
1239 As fonts are scaled, the do end up a little different, so you don't
1240 get exactly the same picture as you scale up and doen, but it's
1243 On wxGTK1 on my Linux system, at least, using a font of over about
1244 3000 pts. brings the system to a halt. It's the Font Server using
1245 huge amounts of memory. My work around is to max the font size to
1246 3000 points, so it won't scale past there. GTK2 uses smarter font
1247 drawing, so that may not be an issue in future versions, so feel
1248 free to test. Another smarter way to do it would be to set a global
1249 zoom limit at that point.
1251 The hit-test is done on the entire text extent. This could be made
1252 optional, but I haven't gotten around to it.
1256 def __init__(self
, String
, XY
, Size
,
1258 BackgroundColor
= None,
1265 InForeground
= False):
1267 DrawObject
.__init
__(self
,InForeground
)
1269 self
.String
= String
1270 self
.XY
= array( XY
, Float
)
1271 self
.XY
.shape
= (2,)
1274 self
.BackgroundColor
= BackgroundColor
1275 self
.Family
= Family
1277 self
.Weight
= Weight
1278 self
.Underline
= Underline
1282 self
.FaceName
= Font
.GetFaceName()
1283 self
.Family
= Font
.GetFamily()
1284 self
.Style
= Font
.GetStyle()
1285 self
.Underlined
= Font
.GetUnderlined()
1286 self
.Weight
= Font
.GetWeight()
1288 # Experimental max font size value on wxGTK2: this works OK on
1289 # my system. If it's a lot larger, there is a crash, with the
1292 # The application 'FloatCanvasDemo.py' lost its
1293 # connection to the display :0.0; most likely the X server was
1294 # shut down or you killed/destroyed the application.
1296 # Windows and OS-X seem to be better behaved in this regard.
1297 # They may not draw it, but they don't crash either!
1298 self
.MaxFontSize
= 1000
1300 self
.ShiftFun
= self
.ShiftFunDict
[Position
]
1302 self
.CalcBoundingBox()
1304 def LayoutText(self
):
1305 # This will be called when the text is re-set
1306 # nothing much to be done here
1307 self
.CalcBoundingBox()
1309 def CalcBoundingBox(self
):
1310 ## this isn't exact, as fonts don't scale exactly.
1312 bitmap
= wx
.EmptyBitmap(1, 1)
1313 dc
.SelectObject(bitmap
) #wxMac needs a Bitmap selected for GetTextExtent to work.
1314 DrawingSize
= 40 # pts This effectively determines the resolution that the BB is computed to.
1315 ScaleFactor
= float(self
.Size
) / DrawingSize
1316 dc
.SetFont(self
.SetFont(DrawingSize
, self
.Family
, self
.Style
, self
.Weight
, self
.Underline
, self
.FaceName
) )
1317 (w
,h
) = dc
.GetTextExtent(self
.String
)
1320 x
, y
= self
.ShiftFun(self
.XY
[0], self
.XY
[1], w
, h
, world
= 1)
1321 self
.BoundingBox
= array(((x
, y
-h
),(x
+ w
, y
)),Float
)
1323 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
1324 (X
,Y
) = WorldToPixel( (self
.XY
) )
1326 # compute the font size:
1327 Size
= abs( ScaleWorldToPixel( (self
.Size
, self
.Size
) )[1] ) # only need a y coordinate length
1328 ## Check to see if the font size is large enough to blow up the X font server
1329 ## If so, limit it. Would it be better just to not draw it?
1330 ## note that this limit is dependent on how much memory you have, etc.
1331 Size
= min(Size
, self
.MaxFontSize
)
1332 dc
.SetFont(self
.SetFont(Size
, self
.Family
, self
.Style
, self
.Weight
, self
.Underline
, self
.FaceName
))
1333 dc
.SetTextForeground(self
.Color
)
1334 if self
.BackgroundColor
:
1335 dc
.SetBackgroundMode(wx
.SOLID
)
1336 dc
.SetTextBackground(self
.BackgroundColor
)
1338 dc
.SetBackgroundMode(wx
.TRANSPARENT
)
1339 (w
,h
) = dc
.GetTextExtent(self
.String
)
1340 # compute the shift, and adjust the coordinates, if neccesary
1341 # This had to be put in here, because it changes with Zoom, as
1342 # fonts don't scale exactly.
1343 xy
= self
.ShiftFun(X
, Y
, w
, h
)
1345 dc
.DrawTextPoint(self
.String
, xy
)
1346 if HTdc
and self
.HitAble
:
1347 HTdc
.SetPen(self
.HitPen
)
1348 HTdc
.SetBrush(self
.HitBrush
)
1349 HTdc
.DrawRectanglePointSize(xy
, (w
, h
) )
1351 class ScaledTextBox(DrawObject
, TextObjectMixin
):
1353 This class creates a TextBox object that is scaled when zoomed. It is
1354 placed at the coordinates, x,y.
1356 If the Width parameter is defined, the text will be wrapped to the width given.
1358 A Box can be drawn around the text, be specifying:
1359 LineWidth and/or FillColor
1361 A space(margin) can be put all the way around the text, be specifying:
1362 the PadSize argument in world coordinates.
1364 The spacing between lines can be adjusted with the:
1365 LineSpacing argument.
1367 The "Position" argument is a two character string, indicating where
1368 in relation to the coordinates the Box should be oriented.
1369 -The first letter is: t, c, or b, for top, center and bottom.
1370 -The second letter is: l, c, or r, for left, center and right The
1371 position refers to the position relative to the text itself. It
1372 defaults to "tl" (top left).
1374 Size is the size of the font in world coordinates.
1377 Font family, a generic way of referring to fonts without
1378 specifying actual facename. One of:
1379 wx.DEFAULT: Chooses a default font.
1380 wx.DECORATIVE: A decorative font.
1381 wx.ROMAN: A formal, serif font.
1382 wx.SCRIPT: A handwriting font.
1383 wx.SWISS: A sans-serif font.
1384 wx.MODERN: A fixed pitch font.
1385 NOTE: these are only as good as the wxWindows defaults, which aren't so good.
1387 One of wx.NORMAL, wx.SLANT and wx.ITALIC.
1389 One of wx.NORMAL, wx.LIGHT and wx.BOLD.
1391 The value can be True or False. At present this may have an an
1392 effect on Windows only.
1394 Alternatively, you can set the kw arg: Font, to a wx.Font, and the
1395 above will be ignored. The size of the font you specify will be
1396 ignored, but the rest of its attributes will be preserved.
1398 The size will scale as the drawing is zoomed.
1402 As fonts are scaled, they do end up a little different, so you don't
1403 get exactly the same picture as you scale up and down, but it's
1406 On wxGTK1 on my Linux system, at least, using a font of over about
1407 1000 pts. brings the system to a halt. It's the Font Server using
1408 huge amounts of memory. My work around is to max the font size to
1409 1000 points, so it won't scale past there. GTK2 uses smarter font
1410 drawing, so that may not be an issue in future versions, so feel
1411 free to test. Another smarter way to do it would be to set a global
1412 zoom limit at that point.
1414 The hit-test is done on the entire box. This could be made
1415 optional, but I haven't gotten around to it.
1419 def __init__(self
, String
,
1423 BackgroundColor
= None,
1424 LineColor
= 'Black',
1425 LineStyle
= 'Solid',
1437 InForeground
= False):
1439 DrawObject
.__init
__(self
,InForeground
)
1441 self
.XY
= array(Point
, Float
)
1444 self
.BackgroundColor
= BackgroundColor
1445 self
.LineColor
= LineColor
1446 self
.LineStyle
= LineStyle
1447 self
.LineWidth
= LineWidth
1449 if PadSize
is None: # the default is just a little bit of padding
1450 self
.PadSize
= Size
/10.0
1452 self
.PadSize
= float(PadSize
)
1453 self
.Family
= Family
1455 self
.Weight
= Weight
1456 self
.Underline
= Underline
1457 self
.Alignment
= Alignment
.lower()
1458 self
.LineSpacing
= float(LineSpacing
)
1459 self
.Position
= Position
1464 self
.FaceName
= Font
.GetFaceName()
1465 self
.Family
= Font
.GetFamily()
1466 self
.Style
= Font
.GetStyle()
1467 self
.Underlined
= Font
.GetUnderlined()
1468 self
.Weight
= Font
.GetWeight()
1470 # Experimental max font size value on wxGTK2: this works OK on
1471 # my system. If it's a lot larger, there is a crash, with the
1474 # The application 'FloatCanvasDemo.py' lost its
1475 # connection to the display :0.0; most likely the X server was
1476 # shut down or you killed/destroyed the application.
1478 # Windows and OS-X seem to be better behaved in this regard.
1479 # They may not draw it, but they don't crash either!
1481 self
.MaxFontSize
= 1000
1482 self
.ShiftFun
= self
.ShiftFunDict
[Position
]
1484 self
.String
= String
1486 self
.CalcBoundingBox()
1488 self
.SetPen(LineColor
,LineStyle
,LineWidth
)
1489 self
.SetBrush(BackgroundColor
, "Solid")
1492 def WrapToWidth(self
):
1494 bitmap
= wx
.EmptyBitmap(1, 1)
1495 dc
.SelectObject(bitmap
) #wxMac needs a Bitmap selected for GetTextExtent to work.
1496 DrawingSize
= self
.LayoutFontSize
# pts This effectively determines the resolution that the BB is computed to.
1497 ScaleFactor
= float(self
.Size
) / DrawingSize
1498 Width
= (self
.Width
- 2*self
.PadSize
) / ScaleFactor
#Width to wrap to
1499 dc
.SetFont(self
.SetFont(DrawingSize
, self
.Family
, self
.Style
, self
.Weight
, self
.Underline
, self
.FaceName
) )
1502 for s
in self
.Strings
:
1510 w
= dc
.GetTextExtent(' ' + text
[-1])[0]
1511 if LineLength
+ w
<= Width
:
1514 LineLength
= dc
.GetTextExtent(NewText
)[0]
1516 NewStrings
.append(NewText
)
1518 LineLength
= dc
.GetTextExtent(text
[-1])[0]
1520 NewStrings
.append(NewText
)
1521 self
.Strings
= NewStrings
1523 def ReWrap(self
, Width
):
1527 def LayoutText(self
):
1530 Calculates the positions of the words of text.
1532 This isn't exact, as fonts don't scale exactly.
1533 To help this, the position of each individual word
1534 is stored separately, so that the general layout stays
1535 the same in world coordinates, as the fonts scale.
1538 self
.Strings
= self
.String
.split("\n")
1543 bitmap
= wx
.EmptyBitmap(1, 1)
1544 dc
.SelectObject(bitmap
) #wxMac needs a Bitmap selected for GetTextExtent to work.
1546 DrawingSize
= self
.LayoutFontSize
# pts This effectively determines the resolution that the BB is computed to.
1547 ScaleFactor
= float(self
.Size
) / DrawingSize
1549 dc
.SetFont(self
.SetFont(DrawingSize
, self
.Family
, self
.Style
, self
.Weight
, self
.Underline
, self
.FaceName
) )
1551 TextHeight
= dc
.GetTextExtent("X")[1]
1552 SpaceWidth
= dc
.GetTextExtent(" ")[0]
1553 LineHeight
= TextHeight
* self
.LineSpacing
1555 LineWidths
= zeros((len(self
.Strings
),), Float
)
1560 for i
, s
in enumerate(self
.Strings
):
1562 LineWords
= s
.split(" ")
1563 LinePoints
= zeros((len(LineWords
),2), Float
)
1564 for j
, word
in enumerate(LineWords
):
1566 LineWidths
[i
] += SpaceWidth
1568 LinePoints
[j
] = (LineWidths
[i
], y
)
1569 w
= dc
.GetTextExtent(word
)[0]
1572 AllLinePoints
.append(LinePoints
)
1573 TextWidth
= maximum
.reduce(LineWidths
)
1576 if self
.Width
is None:
1577 BoxWidth
= TextWidth
* ScaleFactor
+ 2*self
.PadSize
1578 else: # use the defined Width
1579 BoxWidth
= self
.Width
1580 Points
= zeros((0,2), Float
)
1582 for i
, LinePoints
in enumerate(AllLinePoints
):
1583 ## Scale to World Coords.
1584 LinePoints
*= (ScaleFactor
, ScaleFactor
)
1585 if self
.Alignment
== 'left':
1586 LinePoints
[:,0] += self
.PadSize
1587 elif self
.Alignment
== 'center':
1588 LinePoints
[:,0] += (BoxWidth
- LineWidths
[i
]*ScaleFactor
)/2.0
1589 elif self
.Alignment
== 'right':
1590 LinePoints
[:,0] += (BoxWidth
- LineWidths
[i
]*ScaleFactor
-self
.PadSize
)
1591 Points
= concatenate((Points
, LinePoints
))
1593 BoxHeight
= -(Points
[-1,1] - (TextHeight
* ScaleFactor
)) + 2*self
.PadSize
1594 (x
,y
) = self
.ShiftFun(self
.XY
[0], self
.XY
[1], BoxWidth
, BoxHeight
, world
=1)
1595 Points
+= (0, -self
.PadSize
)
1596 self
.Points
= Points
1597 self
.BoxWidth
= BoxWidth
1598 self
.BoxHeight
= BoxHeight
1599 self
.CalcBoundingBox()
1601 def CalcBoundingBox(self
):
1605 Calculates the Bounding Box
1609 w
, h
= self
.BoxWidth
, self
.BoxHeight
1610 x
, y
= self
.ShiftFun(self
.XY
[0], self
.XY
[1], w
, h
, world
=1)
1611 self
.BoundingBox
= array(((x
, y
-h
),(x
+ w
, y
)),Float
)
1613 def GetBoxRect(self
):
1614 wh
= (self
.BoxWidth
, self
.BoxHeight
)
1615 xy
= (self
.BoundingBox
[0,0], self
.BoundingBox
[1,1])
1619 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
1620 xy
, wh
= self
.GetBoxRect()
1622 Points
= self
.Points
+ xy
1623 Points
= WorldToPixel(Points
)
1624 xy
= WorldToPixel(xy
)
1625 wh
= ScaleWorldToPixel(wh
) * (1,-1)
1627 # compute the font size:
1628 Size
= abs( ScaleWorldToPixel( (self
.Size
, self
.Size
) )[1] ) # only need a y coordinate length
1629 ## Check to see if the font size is large enough to blow up the X font server
1630 ## If so, limit it. Would it be better just to not draw it?
1631 ## note that this limit is dependent on how much memory you have, etc.
1632 Size
= min(Size
, self
.MaxFontSize
)
1634 dc
.SetFont(self
.SetFont(Size
, self
.Family
, self
.Style
, self
.Weight
, self
.Underline
, self
.FaceName
))
1635 dc
.SetTextForeground(self
.Color
)
1636 dc
.SetBackgroundMode(wx
.TRANSPARENT
)
1639 if (self
.LineStyle
and self
.LineColor
) or self
.BackgroundColor
:
1640 dc
.SetBrush(self
.Brush
)
1642 dc
.DrawRectanglePointSize(xy
, wh
)
1645 dc
.DrawTextList(self
.Words
, Points
)
1648 if HTdc
and self
.HitAble
:
1649 HTdc
.SetPen(self
.HitPen
)
1650 HTdc
.SetBrush(self
.HitBrush
)
1651 HTdc
.DrawRectanglePointSize(xy
, wh
)
1653 class Bitmap(DrawObject
, TextObjectMixin
):
1655 This class creates a bitmap object, placed at the coordinates,
1656 x,y. the "Position" argument is a two charactor string, indicating
1657 where in relation to the coordinates the bitmap should be oriented.
1659 The first letter is: t, c, or b, for top, center and bottom The
1660 second letter is: l, c, or r, for left, center and right The
1661 position refers to the position relative to the text itself. It
1662 defaults to "tl" (top left).
1664 The size is fixed, and does not scale with the drawing.
1668 def __init__(self
,Bitmap
,XY
,
1670 InForeground
= False):
1672 DrawObject
.__init
__(self
,InForeground
)
1674 if type(Bitmap
) == wx
._gdi
.Bitmap
:
1675 self
.Bitmap
= Bitmap
1676 elif type(Bitmap
) == wx
._core
.Image
:
1677 self
.Bitmap
= wx
.BitmapFromImage(Bitmap
)
1679 # Note the BB is just the point, as the size in World coordinates is not fixed
1680 self
.BoundingBox
= array((XY
,XY
),Float
)
1684 (self
.Width
, self
.Height
) = self
.Bitmap
.GetWidth(), self
.Bitmap
.GetHeight()
1685 self
.ShiftFun
= self
.ShiftFunDict
[Position
]
1687 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
1688 XY
= WorldToPixel(self
.XY
)
1689 XY
= self
.ShiftFun(XY
[0], XY
[1], self
.Width
, self
.Height
)
1690 dc
.DrawBitmapPoint(self
.Bitmap
, XY
, True)
1691 if HTdc
and self
.HitAble
:
1692 HTdc
.SetPen(self
.HitPen
)
1693 HTdc
.SetBrush(self
.HitBrush
)
1694 HTdc
.DrawRectanglePointSize(XY
, (self
.Width
, self
.Height
) )
1696 class ScaledBitmap(DrawObject
, TextObjectMixin
):
1699 This class creates a bitmap object, placed at the coordinates, XY,
1700 of Height, H, in World coorsinates. The width is calculated from the
1701 aspect ratio of the bitmap.
1703 the "Position" argument is a two charactor string, indicating
1704 where in relation to the coordinates the bitmap should be oriented.
1706 The first letter is: t, c, or b, for top, center and bottom The
1707 second letter is: l, c, or r, for left, center and right The
1708 position refers to the position relative to the text itself. It
1709 defaults to "tl" (top left).
1711 The size scales with the drawing
1720 InForeground
= False):
1722 DrawObject
.__init
__(self
,InForeground
)
1724 if type(Bitmap
) == wx
._gdi
.Bitmap
:
1725 self
.Image
= Bitmap
.ConvertToImage()
1726 elif type(Bitmap
) == wx
._core
.Image
:
1730 self
.Height
= Height
1731 (self
.bmpWidth
, self
.bmpHeight
) = self
.Image
.GetWidth(), self
.Image
.GetHeight()
1732 self
.Width
= self
.bmpWidth
/ self
.bmpHeight
* Height
1733 self
.ShiftFun
= self
.ShiftFunDict
[Position
]
1734 self
.CalcBoundingBox()
1735 self
.ScaledBitmap
= None
1736 self
.ScaledHeight
= None
1738 def CalcBoundingBox(self
):
1739 ## this isn't exact, as fonts don't scale exactly.
1740 w
,h
= self
.Width
, self
.Height
1741 x
, y
= self
.ShiftFun(self
.XY
[0], self
.XY
[1], w
, h
, world
= 1)
1742 self
.BoundingBox
= array(((x
, y
-h
),(x
+ w
, y
)),Float
)
1744 def _Draw(self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
=None):
1745 XY
= WorldToPixel(self
.XY
)
1746 H
= ScaleWorldToPixel(self
.Height
)[0]
1747 W
= H
* (self
.bmpWidth
/ self
.bmpHeight
)
1748 if (self
.ScaledBitmap
is None) or (H
<> self
.ScaledHeight
) :
1749 self
.ScaledHeight
= H
1750 Img
= self
.Image
.Scale(W
, H
)
1751 self
.ScaledBitmap
= wx
.BitmapFromImage(Img
)
1753 XY
= self
.ShiftFun(XY
[0], XY
[1], W
, H
)
1754 dc
.DrawBitmapPoint(self
.ScaledBitmap
, XY
, True)
1755 if HTdc
and self
.HitAble
:
1756 HTdc
.SetPen(self
.HitPen
)
1757 HTdc
.SetBrush(self
.HitBrush
)
1758 HTdc
.DrawRectanglePointSize(XY
, (W
, H
) )
1761 #---------------------------------------------------------------------------
1762 class FloatCanvas(wx
.Panel
):
1763 ## fixme: could this be a wx.Window?
1767 This is a high level window for drawing maps and anything else in an
1768 arbitrary coordinate system.
1770 The goal is to provide a convenient way to draw stuff on the screen
1771 without having to deal with handling OnPaint events, converting to pixel
1772 coordinates, knowing about wxWindows brushes, pens, and colors, etc. It
1773 also provides virtually unlimited zooming and scrolling
1775 I am using it for two things:
1776 1) general purpose drawing in floating point coordinates
1777 2) displaying map data in Lat-long coordinates
1779 If the projection is set to None, it will draw in general purpose
1780 floating point coordinates. If the projection is set to 'FlatEarth', it
1781 will draw a FlatEarth projection, centered on the part of the map that
1782 you are viewing. You can also pass in your own projection function.
1784 It is double buffered, so re-draws after the window is uncovered by something
1785 else are very quick.
1787 It relies on NumPy, which is needed for speed (maybe, I havn't profiled it)
1789 Bugs and Limitations:
1790 Lots: patches, fixes welcome
1792 For Map drawing: It ignores the fact that the world is, in fact, a
1793 sphere, so it will do strange things if you are looking at stuff near
1794 the poles or the date line. so far I don't have a need to do that, so I
1795 havn't bothered to add any checks for that yet.
1798 I have set no zoom limits. What this means is that if you zoom in really
1799 far, you can get integer overflows, and get wierd results. It
1800 doesn't seem to actually cause any problems other than wierd output, at
1801 least when I have run it.
1804 I have done a couple of things to improve speed in this app. The one
1805 thing I have done is used NumPy Arrays to store the coordinates of the
1806 points of the objects. This allowed me to use array oriented functions
1807 when doing transformations, and should provide some speed improvement
1808 for objects with a lot of points (big polygons, polylines, pointsets).
1810 The real slowdown comes when you have to draw a lot of objects, because
1811 you have to call the wx.DC.DrawSomething call each time. This is plenty
1812 fast for tens of objects, OK for hundreds of objects, but pretty darn
1813 slow for thousands of objects.
1815 The solution is to be able to pass some sort of object set to the DC
1816 directly. I've used DC.DrawPointList(Points), and it helped a lot with
1817 drawing lots of points. I havn't got a LineSet type object, so I havn't
1818 used DC.DrawLineList yet. I'd like to get a full set of DrawStuffList()
1819 methods implimented, and then I'd also have a full set of Object sets
1820 that could take advantage of them. I hope to get to it some day.
1824 At this point, there are a full set of custom mouse events. They are
1825 just like the rebulsr mouse events, but include an extra attribute:
1826 Event.GetCoords(), that returns the (x,y) position in world
1827 coordinates, as a length-2 NumPy vector of Floats.
1829 Copyright: Christopher Barker
1831 License: Same as the version of wxPython you are using it with
1833 Please let me know if you're using this!!!
1837 Chris.Barker@noaa.gov
1841 def __init__(self
, parent
, id = -1,
1842 size
= wx
.DefaultSize
,
1843 ProjectionFun
= None,
1844 BackgroundColor
= "WHITE",
1847 wx
.Panel
.__init
__( self
, parent
, id, wx
.DefaultPosition
, size
)
1849 global ScreenPPI
## A global variable to hold the Pixels per inch that wxWindows thinks is in use.
1851 ScreenPPI
= dc
.GetPPI()[1] # Pixel height
1854 self
.HitColorGenerator
= None
1855 self
.UseHitTest
= None
1857 self
.NumBetweenBlits
= 500
1859 self
.BackgroundBrush
= wx
.Brush(BackgroundColor
,wx
.SOLID
)
1863 wx
.EVT_PAINT(self
, self
.OnPaint
)
1864 wx
.EVT_SIZE(self
, self
.OnSize
)
1866 wx
.EVT_LEFT_DOWN(self
, self
.LeftDownEvent
)
1867 wx
.EVT_LEFT_UP(self
, self
.LeftUpEvent
)
1868 wx
.EVT_LEFT_DCLICK(self
, self
.LeftDoubleClickEvent
)
1869 wx
.EVT_MIDDLE_DOWN(self
, self
.MiddleDownEvent
)
1870 wx
.EVT_MIDDLE_UP(self
, self
.MiddleUpEvent
)
1871 wx
.EVT_MIDDLE_DCLICK(self
, self
.MiddleDoubleClickEvent
)
1872 wx
.EVT_RIGHT_DOWN(self
, self
.RightDownEvent
)
1873 wx
.EVT_RIGHT_UP(self
, self
.RightUpEvent
)
1874 wx
.EVT_RIGHT_DCLICK(self
, self
.RightDoubleCLickEvent
)
1875 wx
.EVT_MOTION(self
, self
.MotionEvent
)
1876 wx
.EVT_MOUSEWHEEL(self
, self
.WheelEvent
)
1878 ## CHB: I'm leaving these out for now.
1879 #wx.EVT_ENTER_WINDOW(self, self. )
1880 #wx.EVT_LEAVE_WINDOW(self, self. )
1882 ## create the Hit Test Dicts:
1887 self
._ForeDrawList
= []
1888 self
._ForegroundBuffer
= None
1889 self
.BoundingBox
= None
1890 self
.BoundingBoxDirty
= False
1891 self
.ViewPortCenter
= array( (0,0), Float
)
1893 self
.SetProjectionFun(ProjectionFun
)
1895 self
.MapProjectionVector
= array( (1,1), Float
) # No Projection to start!
1896 self
.TransformVector
= array( (1,-1), Float
) # default Transformation
1901 self
.StartRBBox
= None
1902 self
.PrevRBBox
= None
1903 self
.StartMove
= None
1904 self
.PrevMoveXY
= None
1905 self
.ObjectUnderMouse
= None
1907 # called just to make sure everything is initialized
1912 self
.CreateCursors()
1914 def CreateCursors(self
):
1916 ## create all the Cursors, so they don't need to be created each time.
1918 if "wxMac" in wx
.PlatformInfo
: # use 16X16 cursors for wxMac
1919 self
.HandCursor
= wx
.CursorFromImage(Resources
.getHand16Image())
1920 self
.GrabHandCursor
= wx
.CursorFromImage(Resources
.getGrabHand16Image())
1922 img
= Resources
.getMagPlus16Image()
1923 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_X
, 6)
1924 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_Y
, 6)
1925 self
.MagPlusCursor
= wx
.CursorFromImage(img
)
1927 img
= Resources
.getMagMinus16Image()
1928 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_X
, 6)
1929 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_Y
, 6)
1930 self
.MagMinusCursor
= wx
.CursorFromImage(img
)
1931 else: # use 24X24 cursors for GTK and Windows
1932 self
.HandCursor
= wx
.CursorFromImage(Resources
.getHandImage())
1933 self
.GrabHandCursor
= wx
.CursorFromImage(Resources
.getGrabHandImage())
1935 img
= Resources
.getMagPlusImage()
1936 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_X
, 9)
1937 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_Y
, 9)
1938 self
.MagPlusCursor
= wx
.CursorFromImage(img
)
1940 img
= Resources
.getMagMinusImage()
1941 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_X
, 9)
1942 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_Y
, 9)
1943 self
.MagMinusCursor
= wx
.CursorFromImage(img
)
1945 def SetProjectionFun(self
,ProjectionFun
):
1946 if ProjectionFun
== 'FlatEarth':
1947 self
.ProjectionFun
= self
.FlatEarthProjection
1948 elif callable(ProjectionFun
):
1949 self
.ProjectionFun
= ProjectionFun
1950 elif ProjectionFun
is None:
1951 self
.ProjectionFun
= lambda x
=None: array( (1,1), Float
)
1953 raise FloatCanvasError('Projectionfun must be either: "FlatEarth", None, or a callable object (function, for instance) that takes the ViewPortCenter and returns a MapProjectionVector')
1955 def FlatEarthProjection(self
, CenterPoint
):
1956 return array((cos(pi
*CenterPoint
[1]/180),1),Float
)
1958 def SetMode(self
,Mode
):
1959 if Mode
in ["ZoomIn","ZoomOut","Move","Mouse", None]:
1961 self
.SetCursor(self
.HandCursor
)
1962 elif Mode
== "ZoomIn":
1963 self
.SetCursor(self
.MagPlusCursor
)
1964 elif Mode
== "ZoomOut":
1965 self
.SetCursor(self
.MagMinusCursor
)
1967 self
.SetCursor(wx
.NullCursor
)
1972 raise FloatCanvasError('"%s" is Not a valid Mode'%Mode
)
1974 def MakeHitDict(self
):
1975 ##fixme: Should this just be None if nothing has been bound?
1976 self
.HitDict
= {EVT_FC_LEFT_DOWN: {}
,
1978 EVT_FC_LEFT_DCLICK
: {},
1979 EVT_FC_MIDDLE_DOWN
: {},
1980 EVT_FC_MIDDLE_UP
: {},
1981 EVT_FC_MIDDLE_DCLICK
: {},
1982 EVT_FC_RIGHT_DOWN
: {},
1983 EVT_FC_RIGHT_UP
: {},
1984 EVT_FC_RIGHT_DCLICK
: {},
1985 EVT_FC_ENTER_OBJECT
: {},
1986 EVT_FC_LEAVE_OBJECT
: {},
1989 def _RaiseMouseEvent(self
, Event
, EventType
):
1991 This is called in various other places to raise a Mouse Event
1993 #print "in Raise Mouse Event", Event
1994 pt
= self
.PixelToWorld( Event
.GetPosition() )
1995 evt
= _MouseEvent(EventType
, Event
, self
.GetId(), pt
)
1996 self
.GetEventHandler().ProcessEvent(evt
)
1998 def HitTest(self
, event
, HitEvent
):
2000 # check if there are any objects in the dict for this event
2001 if self
.HitDict
[ HitEvent
]:
2002 xy
= event
.GetPosition()
2003 if self
._ForegroundHTdc
:
2004 hitcolor
= self
._ForegroundHTdc
.GetPixelPoint( xy
)
2006 hitcolor
= self
._HTdc
.GetPixelPoint( xy
)
2007 color
= ( hitcolor
.Red(), hitcolor
.Green(), hitcolor
.Blue() )
2008 if color
in self
.HitDict
[ HitEvent
]:
2009 Object
= self
.HitDict
[ HitEvent
][color
]
2010 ## Add the hit coords to the Object
2011 Object
.HitCoords
= self
.PixelToWorld( xy
)
2012 Object
.HitCoordsPixel
= xy
2013 Object
.CallBackFuncs
[HitEvent
](Object
)
2017 def MouseOverTest(self
, event
):
2018 ##fixme: Can this be cleaned up?
2020 xy
= event
.GetPosition()
2021 if self
._ForegroundHTdc
:
2022 hitcolor
= self
._ForegroundHTdc
.GetPixelPoint( xy
)
2024 hitcolor
= self
._HTdc
.GetPixelPoint( xy
)
2025 color
= ( hitcolor
.Red(), hitcolor
.Green(), hitcolor
.Blue() )
2026 OldObject
= self
.ObjectUnderMouse
2027 ObjectCallbackCalled
= False
2028 if color
in self
.HitDict
[ EVT_FC_ENTER_OBJECT
]:
2029 Object
= self
.HitDict
[ EVT_FC_ENTER_OBJECT
][color
]
2030 if (OldObject
is None):
2032 Object
.CallBackFuncs
[EVT_FC_ENTER_OBJECT
](Object
)
2033 ObjectCallbackCalled
= True
2035 pass # this means the enter event isn't bound for that object
2036 elif OldObject
== Object
: # the mouse is still on the same object
2038 ## Is the mouse on a differnt object as it was...
2039 elif not (Object
== OldObject
):
2040 # call the leave object callback
2042 OldObject
.CallBackFuncs
[EVT_FC_LEAVE_OBJECT
](OldObject
)
2043 ObjectCallbackCalled
= True
2045 pass # this means the leave event isn't bound for that object
2047 Object
.CallBackFuncs
[EVT_FC_ENTER_OBJECT
](Object
)
2048 ObjectCallbackCalled
= True
2050 pass # this means the enter event isn't bound for that object
2051 ## set the new object under mouse
2052 self
.ObjectUnderMouse
= Object
2053 elif color
in self
.HitDict
[ EVT_FC_LEAVE_OBJECT
]:
2054 Object
= self
.HitDict
[ EVT_FC_LEAVE_OBJECT
][color
]
2055 self
.ObjectUnderMouse
= Object
2057 # no objects under mouse bound to mouse-over events
2058 self
.ObjectUnderMouse
= None
2061 OldObject
.CallBackFuncs
[EVT_FC_LEAVE_OBJECT
](OldObject
)
2062 ObjectCallbackCalled
= True
2064 pass # this means the leave event isn't bound for that object
2065 return ObjectCallbackCalled
2068 ## fixme: There is a lot of repeated code here
2069 ## Is there a better way?
2070 def LeftDoubleClickEvent(self
,event
):
2071 if self
.GUIMode
== "Mouse":
2072 EventType
= EVT_FC_LEFT_DCLICK
2073 if not self
.HitTest(event
, EventType
):
2074 self
._RaiseMouseEvent
(event
, EventType
)
2076 def MiddleDownEvent(self
,event
):
2077 if self
.GUIMode
== "Mouse":
2078 EventType
= EVT_FC_MIDDLE_DOWN
2079 if not self
.HitTest(event
, EventType
):
2080 self
._RaiseMouseEvent
(event
, EventType
)
2082 def MiddleUpEvent(self
,event
):
2083 if self
.GUIMode
== "Mouse":
2084 EventType
= EVT_FC_MIDDLE_UP
2085 if not self
.HitTest(event
, EventType
):
2086 self
._RaiseMouseEvent
(event
, EventType
)
2088 def MiddleDoubleClickEvent(self
,event
):
2089 if self
.GUIMode
== "Mouse":
2090 EventType
= EVT_FC_MIDDLE_DCLICK
2091 if not self
.HitTest(event
, EventType
):
2092 self
._RaiseMouseEvent
(event
, EventType
)
2094 def RightUpEvent(self
,event
):
2095 if self
.GUIMode
== "Mouse":
2096 EventType
= EVT_FC_RIGHT_UP
2097 if not self
.HitTest(event
, EventType
):
2098 self
._RaiseMouseEvent
(event
, EventType
)
2100 def RightDoubleCLickEvent(self
,event
):
2101 if self
.GUIMode
== "Mouse":
2102 EventType
= EVT_FC_RIGHT_DCLICK
2103 if not self
.HitTest(event
, EventType
):
2104 self
._RaiseMouseEvent
(event
, EventType
)
2106 def WheelEvent(self
,event
):
2107 ##if self.GUIMode == "Mouse":
2108 ## Why not always raise this?
2109 self
._RaiseMouseEvent
(event
, EVT_FC_MOUSEWHEEL
)
2112 def LeftDownEvent(self
,event
):
2114 if self
.GUIMode
== "ZoomIn":
2115 self
.StartRBBox
= array( event
.GetPosition() )
2116 self
.PrevRBBox
= None
2118 elif self
.GUIMode
== "ZoomOut":
2119 Center
= self
.PixelToWorld( event
.GetPosition() )
2120 self
.Zoom(1/1.5,Center
)
2121 elif self
.GUIMode
== "Move":
2122 self
.SetCursor(self
.GrabHandCursor
)
2123 self
.StartMove
= array( event
.GetPosition() )
2124 self
.PrevMoveXY
= (0,0)
2125 elif self
.GUIMode
== "Mouse":
2127 if not self
.HitTest(event
, EVT_FC_LEFT_DOWN
):
2128 self
._RaiseMouseEvent
(event
,EVT_FC_LEFT_DOWN
)
2132 def LeftUpEvent(self
,event
):
2133 if self
.HasCapture():
2136 if self
.GUIMode
== "ZoomIn":
2137 if event
.LeftUp() and not self
.StartRBBox
is None:
2138 self
.PrevRBBox
= None
2139 EndRBBox
= event
.GetPosition()
2140 StartRBBox
= self
.StartRBBox
2141 # if mouse has moved less that ten pixels, don't use the box.
2142 if ( abs(StartRBBox
[0] - EndRBBox
[0]) > 10
2143 and abs(StartRBBox
[1] - EndRBBox
[1]) > 10 ):
2144 EndRBBox
= self
.PixelToWorld(EndRBBox
)
2145 StartRBBox
= self
.PixelToWorld(StartRBBox
)
2146 BB
= array(((min(EndRBBox
[0],StartRBBox
[0]),
2147 min(EndRBBox
[1],StartRBBox
[1])),
2148 (max(EndRBBox
[0],StartRBBox
[0]),
2149 max(EndRBBox
[1],StartRBBox
[1]))),Float
)
2152 Center
= self
.PixelToWorld(StartRBBox
)
2153 self
.Zoom(1.5,Center
)
2154 self
.StartRBBox
= None
2155 elif self
.GUIMode
== "Move":
2156 self
.SetCursor(self
.HandCursor
)
2157 if self
.StartMove
is not None:
2158 StartMove
= self
.StartMove
2159 EndMove
= array((event
.GetX(),event
.GetY()))
2160 if sum((StartMove
-EndMove
)**2) > 16:
2161 self
.MoveImage(StartMove
-EndMove
,'Pixel')
2162 self
.StartMove
= None
2163 elif self
.GUIMode
== "Mouse":
2164 EventType
= EVT_FC_LEFT_UP
2165 if not self
.HitTest(event
, EventType
):
2166 self
._RaiseMouseEvent
(event
, EventType
)
2170 def MotionEvent(self
,event
):
2172 if self
.GUIMode
== "ZoomIn":
2173 if event
.Dragging() and event
.LeftIsDown() and not (self
.StartRBBox
is None):
2174 xy0
= self
.StartRBBox
2175 xy1
= array( event
.GetPosition() )
2177 wh
[0] = max(wh
[0], int(wh
[1]*self
.AspectRatio
))
2178 wh
[1] = int(wh
[0] / self
.AspectRatio
)
2179 xy_c
= (xy0
+ xy1
) / 2
2180 dc
= wx
.ClientDC(self
)
2182 dc
.SetPen(wx
.Pen('WHITE', 2, wx
.SHORT_DASH
))
2183 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2184 dc
.SetLogicalFunction(wx
.XOR
)
2186 dc
.DrawRectanglePointSize(*self
.PrevRBBox
)
2187 self
.PrevRBBox
= ( xy_c
- wh
/2, wh
)
2188 dc
.DrawRectanglePointSize( *self
.PrevRBBox
)
2190 elif self
.GUIMode
== "Move":
2191 if event
.Dragging() and event
.LeftIsDown() and not self
.StartMove
is None:
2192 xy1
= array( event
.GetPosition() )
2194 xy_tl
= xy1
- self
.StartMove
2195 dc
= wx
.ClientDC(self
)
2197 x1
,y1
= self
.PrevMoveXY
2199 w
,h
= self
.PanelSize
2200 ##fixme: This sure could be cleaner!
2201 if x2
> x1
and y2
> y1
:
2208 elif x2
> x1
and y2
<= y1
:
2217 elif x2
<= x1
and y2
> y1
:
2226 elif x2
<= x1
and y2
<= y1
:
2236 dc
.SetPen(wx
.TRANSPARENT_PEN
)
2237 dc
.SetBrush(self
.BackgroundBrush
)
2238 dc
.DrawRectangle(xa
, ya
, wa
, ha
)
2239 dc
.DrawRectangle(xb
, yb
, wb
, hb
)
2240 self
.PrevMoveXY
= xy_tl
2241 if self
._ForeDrawList
:
2242 ##if self._ForegroundBuffer:
2243 dc
.DrawBitmapPoint(self
._ForegroundBuffer
,xy_tl
)
2245 dc
.DrawBitmapPoint(self
._Buffer
,xy_tl
)
2247 elif self
.GUIMode
== "Mouse":
2248 ## Only do something if there are mouse over events bound
2249 if self
.HitDict
and (self
.HitDict
[ EVT_FC_ENTER_OBJECT
] or self
.HitDict
[ EVT_FC_LEAVE_OBJECT
] ):
2250 if not self
.MouseOverTest(event
):
2251 self
._RaiseMouseEvent
(event
,EVT_FC_MOTION
)
2254 self
._RaiseMouseEvent
(event
,EVT_FC_MOTION
)
2258 def RightDownEvent(self
,event
):
2260 if self
.GUIMode
== "ZoomIn":
2261 Center
= self
.PixelToWorld((event
.GetX(),event
.GetY()))
2262 self
.Zoom(1/1.5,Center
)
2263 elif self
.GUIMode
== "ZoomOut":
2264 Center
= self
.PixelToWorld((event
.GetX(),event
.GetY()))
2265 self
.Zoom(1.5,Center
)
2266 elif self
.GUIMode
== "Mouse":
2267 EventType
= EVT_FC_RIGHT_DOWN
2268 if not self
.HitTest(event
, EventType
):
2269 self
._RaiseMouseEvent
(event
, EventType
)
2273 def MakeNewBuffers(self
):
2274 self
._BackgroundDirty
= True
2275 # Make new offscreen bitmap:
2276 self
._Buffer
= wx
.EmptyBitmap(*self
.PanelSize
)
2278 #dc.SelectObject(self._Buffer)
2280 if self
._ForeDrawList
:
2281 self
._ForegroundBuffer
= wx
.EmptyBitmap(*self
.PanelSize
)
2283 self
._ForegroundBuffer
= None
2288 self
._ForegroundHTdc
= None
2290 def MakeNewHTdc(self
):
2291 ## Note: While it's considered a "bad idea" to keep a
2292 ## MemoryDC around I'm doing it here because a wx.Bitmap
2293 ## doesn't have a GetPixel method so a DC is needed to do
2294 ## the hit-test. It didn't seem like a good idea to re-create
2295 ## a wx.MemoryDC on every single mouse event, so I keep it
2297 self
._HTdc
= wx
.MemoryDC()
2298 self
._HTBitmap
= wx
.EmptyBitmap(*self
.PanelSize
)
2299 self
._HTdc
.SelectObject( self
._HTBitmap
)
2300 self
._HTdc
.SetBackground(wx
.BLACK_BRUSH
)
2301 if self
._ForeDrawList
:
2302 self
._ForegroundHTdc
= wx
.MemoryDC()
2303 self
._ForegroundHTBitmap
= wx
.EmptyBitmap(*self
.PanelSize
)
2304 self
._ForegroundHTdc
.SelectObject( self
._ForegroundHTBitmap
)
2305 self
._ForegroundHTdc
.SetBackground(wx
.BLACK_BRUSH
)
2307 self
._ForegroundHTdc
= None
2309 def OnSize(self
,event
):
2310 self
.PanelSize
= array(self
.GetClientSizeTuple(),Int32
)
2311 self
.HalfPanelSize
= self
.PanelSize
/ 2 # lrk: added for speed in WorldToPixel
2312 if self
.PanelSize
[0] == 0 or self
.PanelSize
[1] == 0:
2313 self
.AspectRatio
= 1.0
2315 self
.AspectRatio
= float(self
.PanelSize
[0]) / self
.PanelSize
[1]
2316 self
.MakeNewBuffers()
2319 def OnPaint(self
, event
):
2320 dc
= wx
.PaintDC(self
)
2321 if self
._ForegroundBuffer
:
2322 dc
.DrawBitmap(self
._ForegroundBuffer
,0,0)
2324 dc
.DrawBitmap(self
._Buffer
,0,0)
2326 def Draw(self
, Force
=False):
2328 There is a main buffer set up to double buffer the screen, so
2329 you can get quick re-draws when the window gets uncovered.
2331 If there are any objects in self._ForeDrawList, then the
2332 background gets drawn to a new buffer, and the foreground
2333 objects get drawn on top of it. The final result if blitted to
2334 the screen, and stored for future Paint events. This is done so
2335 that you can have a complicated background, but have something
2336 changing on the foreground, without having to wait for the
2337 background to get re-drawn. This can be used to support simple
2338 animation, for instance.
2341 if sometrue(self
.PanelSize
< 1 ): # it's possible for this to get called before being properly initialized.
2343 if self
.Debug
: start
= clock()
2344 ScreenDC
= wx
.ClientDC(self
)
2345 ViewPortWorld
= ( self
.PixelToWorld((0,0)),
2346 self
.PixelToWorld(self
.PanelSize
) )
2347 ViewPortBB
= array( ( minimum
.reduce(ViewPortWorld
),
2348 maximum
.reduce(ViewPortWorld
) ) )
2350 dc
.SelectObject(self
._Buffer
)
2351 if self
._BackgroundDirty
or Force
:
2352 #print "Background is Dirty"
2353 dc
.SetBackground(self
.BackgroundBrush
)
2357 self
._DrawObjects
(dc
, self
._DrawList
, ScreenDC
, ViewPortBB
, self
._HTdc
)
2358 self
._BackgroundDirty
= False
2360 if self
._ForeDrawList
:
2361 ## If an object was just added to the Foreground, there might not yet be a buffer
2362 if self
._ForegroundBuffer
is None:
2363 self
._ForegroundBuffer
= wx
.EmptyBitmap(self
.PanelSize
[0],
2366 dc
= wx
.MemoryDC() ## I got some strange errors (linewidths wrong) if I didn't make a new DC here
2367 dc
.SelectObject(self
._ForegroundBuffer
)
2368 dc
.DrawBitmap(self
._Buffer
,0,0)
2369 if self
._ForegroundHTdc
is None:
2370 self
._ForegroundHTdc
= wx
.MemoryDC()
2371 self
._ForegroundHTdc
.SelectObject( wx
.EmptyBitmap(
2373 self
.PanelSize
[1]) )
2375 ## blit the background HT buffer to the foreground HT buffer
2376 self
._ForegroundHTdc
.Blit(0, 0,
2377 self
.PanelSize
[0], self
.PanelSize
[1],
2379 self
._DrawObjects
(dc
,
2383 self
._ForegroundHTdc
)
2384 ScreenDC
.Blit(0, 0, self
.PanelSize
[0],self
.PanelSize
[1], dc
, 0, 0)
2385 # If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn
2386 # This seeems out of place, but it works.
2388 ScreenDC
.SetPen(wx
.Pen('WHITE', 2,wx
.SHORT_DASH
))
2389 ScreenDC
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2390 ScreenDC
.SetLogicalFunction(wx
.XOR
)
2391 ScreenDC
.DrawRectanglePointSize(*self
.PrevRBBox
)
2392 if self
.Debug
: print "Drawing took %f seconds of CPU time"%(clock()-start
)
2394 ## Clear the font cache
2395 ## IF you don't do this, the X font server starts to take up Massive amounts of memory
2396 ## This is mostly a problem with very large fonts, that you get with scaled text when zoomed in.
2397 DrawObject
.FontList
= {}
2399 def _ShouldRedraw(DrawList
, ViewPortBB
): # lrk: adapted code from BBCheck
2400 # lrk: Returns the objects that should be redrawn
2404 for Object
in DrawList
:
2405 BB1
= Object
.BoundingBox
2406 if (BB1
[1,0] > BB2
[0,0] and BB1
[0,0] < BB2
[1,0] and
2407 BB1
[1,1] > BB2
[0,1] and BB1
[0,1] < BB2
[1,1]):
2408 redrawlist
.append(Object
)
2410 _ShouldRedraw
= staticmethod(_ShouldRedraw
)
2413 ## def BBCheck(self, BB1, BB2):
2416 ## BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise
2419 ## if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and
2420 ## (BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ):
2425 def MoveImage(self
,shift
,CoordType
):
2427 move the image in the window.
2429 shift is an (x,y) tuple, specifying the amount to shift in each direction
2431 It can be in any of three coordinates: Panel, Pixel, World,
2432 specified by the CoordType parameter
2434 Panel coordinates means you want to shift the image by some
2435 fraction of the size of the displaed image
2437 Pixel coordinates means you want to shift the image by some number of pixels
2439 World coordinates mean you want to shift the image by an amount
2440 in Floating point world coordinates
2444 shift
= asarray(shift
,Float
)
2445 #print "shifting by:", shift
2446 if CoordType
== 'Panel':# convert from panel coordinates
2447 shift
= shift
* array((-1,1),Float
) *self
.PanelSize
/self
.TransformVector
2448 elif CoordType
== 'Pixel': # convert from pixel coordinates
2449 shift
= shift
/self
.TransformVector
2450 elif CoordType
== 'World': # No conversion
2453 raise FloatCanvasError('CoordType must be either "Panel", "Pixel", or "World"')
2455 #print "shifting by:", shift
2457 self
.ViewPortCenter
= self
.ViewPortCenter
+ shift
2458 self
.MapProjectionVector
= self
.ProjectionFun(self
.ViewPortCenter
)
2459 self
.TransformVector
= array((self
.Scale
,-self
.Scale
),Float
) * self
.MapProjectionVector
2460 self
._BackgroundDirty
= True
2463 def Zoom(self
,factor
,center
= None):
2466 Zoom(factor, center) changes the amount of zoom of the image by factor.
2467 If factor is greater than one, the image gets larger.
2468 If factor is less than one, the image gets smaller.
2470 Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming.
2471 If center is not given, the center will stay the same.
2474 self
.Scale
= self
.Scale
*factor
2475 if not center
is None:
2476 self
.ViewPortCenter
= array(center
,Float
)
2477 self
.MapProjectionVector
= self
.ProjectionFun(self
.ViewPortCenter
)
2478 self
.TransformVector
= array((self
.Scale
,-self
.Scale
),Float
) * self
.MapProjectionVector
2479 self
._BackgroundDirty
= True
2482 def ZoomToBB(self
, NewBB
= None, DrawFlag
= True):
2486 Zooms the image to the bounding box given, or to the bounding
2487 box of all the objects on the canvas, if none is given.
2491 if not NewBB
is None:
2494 if self
.BoundingBoxDirty
:
2495 self
._ResetBoundingBox
()
2496 BoundingBox
= self
.BoundingBox
2497 if not BoundingBox
is None:
2498 self
.ViewPortCenter
= array(((BoundingBox
[0,0]+BoundingBox
[1,0])/2,
2499 (BoundingBox
[0,1]+BoundingBox
[1,1])/2 ),Float
)
2500 self
.MapProjectionVector
= self
.ProjectionFun(self
.ViewPortCenter
)
2501 # Compute the new Scale
2502 BoundingBox
= BoundingBox
* self
.MapProjectionVector
2504 self
.Scale
= min(abs(self
.PanelSize
[0] / (BoundingBox
[1,0]-BoundingBox
[0,0])),
2505 abs(self
.PanelSize
[1] / (BoundingBox
[1,1]-BoundingBox
[0,1])) )*0.95
2506 except ZeroDivisionError: # this will happen if the BB has zero width or height
2508 self
.Scale
= (self
.PanelSize
[0] / (BoundingBox
[1,0]-BoundingBox
[0,0]))*0.95
2509 except ZeroDivisionError:
2511 self
.Scale
= (self
.PanelSize
[1] / (BoundingBox
[1,1]-BoundingBox
[0,1]))*0.95
2512 except ZeroDivisionError: #zero size! (must be a single point)
2515 self
.TransformVector
= array((self
.Scale
,-self
.Scale
),Float
)* self
.MapProjectionVector
2517 self
._BackgroundDirty
= True
2520 # Reset the shifting and scaling to defaults when there is no BB
2521 self
.ViewPortCenter
= array( (0,0), Float
)
2522 self
.MapProjectionVector
= array( (1,1), Float
) # No Projection to start!
2523 self
.TransformVector
= array( (1,-1), Float
) # default Transformation
2526 def RemoveObjects(self
, Objects
):
2527 for Object
in Objects
:
2528 self
.RemoveObject(Object
, ResetBB
= False)
2529 self
.BoundingBoxDirty
= True
2531 def RemoveObject(self
, Object
, ResetBB
= True):
2532 ##fixme: Using the list.remove method is kind of slow
2533 if Object
.InForeground
:
2534 self
._ForeDrawList
.remove(Object
)
2535 if not self
._ForeDrawList
:
2536 self
._ForegroundBuffer
= None
2537 self
._ForegroundHTdc
= None
2539 self
._DrawList
.remove(Object
)
2540 self
._BackgroundDirty
= True
2542 self
.BoundingBoxDirty
= True
2544 def ClearAll(self
, ResetBB
= True):
2546 self
._ForeDrawList
= []
2547 self
._BackgroundDirty
= True
2548 self
.HitColorGenerator
= None
2549 self
.UseHitTest
= False
2551 self
._ResetBoundingBox
()
2552 self
.MakeNewBuffers()
2556 ## def _AddBoundingBox(self,NewBB):
2557 ## if self.BoundingBox is None:
2558 ## self.BoundingBox = NewBB
2559 ## self.ZoomToBB(NewBB,DrawFlag = False)
2561 ## self.BoundingBox = array( ( (min(self.BoundingBox[0,0],NewBB[0,0]),
2562 ## min(self.BoundingBox[0,1],NewBB[0,1])),
2563 ## (max(self.BoundingBox[1,0],NewBB[1,0]),
2564 ## max(self.BoundingBox[1,1],NewBB[1,1]))),
2567 def _getboundingbox(bboxarray
): # lrk: added this
2569 upperleft
= minimum
.reduce(bboxarray
[:,0])
2570 lowerright
= maximum
.reduce(bboxarray
[:,1])
2571 return array((upperleft
, lowerright
), Float
)
2573 _getboundingbox
= staticmethod(_getboundingbox
)
2575 def _ResetBoundingBox(self
):
2576 if self
._DrawList
or self
._ForeDrawList
:
2577 bboxarray
= zeros((len(self
._DrawList
)+len(self
._ForeDrawList
), 2, 2),Float
)
2578 i
= -1 # just in case _DrawList is empty
2579 for (i
, BB
) in enumerate(self
._DrawList
):
2580 bboxarray
[i
] = BB
.BoundingBox
2581 for (j
, BB
) in enumerate(self
._ForeDrawList
):
2582 bboxarray
[i
+j
+1] = BB
.BoundingBox
2583 self
.BoundingBox
= self
._getboundingbox
(bboxarray
)
2585 self
.BoundingBox
= None
2586 self
.ViewPortCenter
= array( (0,0), Float
)
2587 self
.TransformVector
= array( (1,-1), Float
)
2588 self
.MapProjectionVector
= array( (1,1), Float
)
2590 self
.BoundingBoxDirty
= False
2592 def PixelToWorld(self
,Points
):
2594 Converts coordinates from Pixel coordinates to world coordinates.
2596 Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates.
2599 return (((asarray(Points
,Float
) - (self
.PanelSize
/2))/self
.TransformVector
) + self
.ViewPortCenter
)
2601 def WorldToPixel(self
,Coordinates
):
2603 This function will get passed to the drawing functions of the objects,
2604 to transform from world to pixel coordinates.
2605 Coordinates should be a NX2 array of (x,y) coordinates, or
2606 a 2-tuple, or sequence of 2-tuples.
2608 #Note: this can be called by users code for various reasons, so asarray is needed.
2609 return (((asarray(Coordinates
,Float
) -
2610 self
.ViewPortCenter
)*self
.TransformVector
)+
2611 (self
.HalfPanelSize
)).astype('i')
2613 def ScaleWorldToPixel(self
,Lengths
):
2615 This function will get passed to the drawing functions of the objects,
2616 to Change a length from world to pixel coordinates.
2618 Lengths should be a NX2 array of (x,y) coordinates, or
2619 a 2-tuple, or sequence of 2-tuples.
2621 return ( (asarray(Lengths
,Float
)*self
.TransformVector
) ).astype('i')
2623 def ScalePixelToWorld(self
,Lengths
):
2625 This function computes a pair of x.y lengths,
2626 to change then from pixel to world coordinates.
2628 Lengths should be a NX2 array of (x,y) coordinates, or
2629 a 2-tuple, or sequence of 2-tuples.
2632 return (asarray(Lengths
,Float
) / self
.TransformVector
)
2634 def AddObject(self
,obj
):
2635 # put in a reference to the Canvas, so remove and other stuff can work
2637 if obj
.InForeground
:
2638 self
._ForeDrawList
.append(obj
)
2639 self
.UseForeground
= True
2641 self
._DrawList
.append(obj
)
2642 self
._BackgroundDirty
= True
2643 self
.BoundingBoxDirty
= True
2646 def _DrawObjects(self
, dc
, DrawList
, ScreenDC
, ViewPortBB
, HTdc
= None):
2648 This is a convenience function;
2649 This function takes the list of objects and draws them to specified
2652 dc
.SetBackground(self
.BackgroundBrush
)
2655 PanelSize0
, PanelSize1
= self
.PanelSize
# for speed
2656 WorldToPixel
= self
.WorldToPixel
# for speed
2657 ScaleWorldToPixel
= self
.ScaleWorldToPixel
# for speed
2658 Blit
= ScreenDC
.Blit
# for speed
2659 NumBetweenBlits
= self
.NumBetweenBlits
# for speed
2660 for i
, Object
in enumerate(self
._ShouldRedraw
(DrawList
, ViewPortBB
)):
2662 Object
._Draw
(dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
)
2663 if (i
+1) % NumBetweenBlits
== 0:
2664 Blit(0, 0, PanelSize0
, PanelSize1
, dc
, 0, 0)
2667 def SaveAsImage(self
, filename
, ImageType
=wx
.BITMAP_TYPE_PNG
):
2670 Saves the current image as an image file. The default is in the
2671 PNG format. Other formats can be spcified using the wx flags:
2676 etc. (see the wx docs for the complete list)
2680 self
._Buffer
.SaveFile(filename
, ImageType
)
2683 def _makeFloatCanvasAddMethods(): ## lrk's code for doing this in module __init__
2684 classnames
= ["Circle", "Ellipse", "Rectangle", "ScaledText", "Polygon",
2685 "Line", "Text", "PointSet","Point", "Arrow","ScaledTextBox",
2686 "SquarePoint","Bitmap", "ScaledBitmap"]
2687 for classname
in classnames
:
2688 klass
= globals()[classname
]
2689 def getaddshapemethod(klass
=klass
):
2690 def addshape(self
, *args
, **kwargs
):
2691 Object
= klass(*args
, **kwargs
)
2692 self
.AddObject(Object
)
2695 addshapemethod
= getaddshapemethod()
2696 methodname
= "Add" + classname
2697 setattr(FloatCanvas
, methodname
, addshapemethod
)
2698 docstring
= "Creates %s and adds its reference to the canvas.\n" % classname
2699 docstring
+= "Argument protocol same as %s class" % classname
2701 docstring
+= ", whose docstring is:\n%s" % klass
.__doc
__
2702 FloatCanvas
.__dict
__[methodname
].__doc
__ = docstring
2704 _makeFloatCanvasAddMethods()