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
1908 # this is a bug on OS-X, maybe it's not required?
1913 self
.CreateCursors()
1915 def CreateCursors(self
):
1917 ## create all the Cursors, so they don't need to be created each time.
1919 if "wxMac" in wx
.PlatformInfo
: # use 16X16 cursors for wxMac
1920 self
.HandCursor
= wx
.CursorFromImage(Resources
.getHand16Image())
1921 self
.GrabHandCursor
= wx
.CursorFromImage(Resources
.getGrabHand16Image())
1923 img
= Resources
.getMagPlus16Image()
1924 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_X
, 6)
1925 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_Y
, 6)
1926 self
.MagPlusCursor
= wx
.CursorFromImage(img
)
1928 img
= Resources
.getMagMinus16Image()
1929 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_X
, 6)
1930 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_Y
, 6)
1931 self
.MagMinusCursor
= wx
.CursorFromImage(img
)
1932 else: # use 24X24 cursors for GTK and Windows
1933 self
.HandCursor
= wx
.CursorFromImage(Resources
.getHandImage())
1934 self
.GrabHandCursor
= wx
.CursorFromImage(Resources
.getGrabHandImage())
1936 img
= Resources
.getMagPlusImage()
1937 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_X
, 9)
1938 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_Y
, 9)
1939 self
.MagPlusCursor
= wx
.CursorFromImage(img
)
1941 img
= Resources
.getMagMinusImage()
1942 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_X
, 9)
1943 img
.SetOptionInt(wx
.IMAGE_OPTION_CUR_HOTSPOT_Y
, 9)
1944 self
.MagMinusCursor
= wx
.CursorFromImage(img
)
1946 def SetProjectionFun(self
,ProjectionFun
):
1947 if ProjectionFun
== 'FlatEarth':
1948 self
.ProjectionFun
= self
.FlatEarthProjection
1949 elif callable(ProjectionFun
):
1950 self
.ProjectionFun
= ProjectionFun
1951 elif ProjectionFun
is None:
1952 self
.ProjectionFun
= lambda x
=None: array( (1,1), Float
)
1954 raise FloatCanvasError('Projectionfun must be either: "FlatEarth", None, or a callable object (function, for instance) that takes the ViewPortCenter and returns a MapProjectionVector')
1956 def FlatEarthProjection(self
, CenterPoint
):
1957 return array((cos(pi
*CenterPoint
[1]/180),1),Float
)
1959 def SetMode(self
,Mode
):
1960 if Mode
in ["ZoomIn","ZoomOut","Move","Mouse", None]:
1962 self
.SetCursor(self
.HandCursor
)
1963 elif Mode
== "ZoomIn":
1964 self
.SetCursor(self
.MagPlusCursor
)
1965 elif Mode
== "ZoomOut":
1966 self
.SetCursor(self
.MagMinusCursor
)
1968 self
.SetCursor(wx
.NullCursor
)
1973 raise FloatCanvasError('"%s" is Not a valid Mode'%Mode
)
1975 def MakeHitDict(self
):
1976 ##fixme: Should this just be None if nothing has been bound?
1977 self
.HitDict
= {EVT_FC_LEFT_DOWN: {}
,
1979 EVT_FC_LEFT_DCLICK
: {},
1980 EVT_FC_MIDDLE_DOWN
: {},
1981 EVT_FC_MIDDLE_UP
: {},
1982 EVT_FC_MIDDLE_DCLICK
: {},
1983 EVT_FC_RIGHT_DOWN
: {},
1984 EVT_FC_RIGHT_UP
: {},
1985 EVT_FC_RIGHT_DCLICK
: {},
1986 EVT_FC_ENTER_OBJECT
: {},
1987 EVT_FC_LEAVE_OBJECT
: {},
1990 def _RaiseMouseEvent(self
, Event
, EventType
):
1992 This is called in various other places to raise a Mouse Event
1994 #print "in Raise Mouse Event", Event
1995 pt
= self
.PixelToWorld( Event
.GetPosition() )
1996 evt
= _MouseEvent(EventType
, Event
, self
.GetId(), pt
)
1997 self
.GetEventHandler().ProcessEvent(evt
)
1999 def HitTest(self
, event
, HitEvent
):
2001 # check if there are any objects in the dict for this event
2002 if self
.HitDict
[ HitEvent
]:
2003 xy
= event
.GetPosition()
2004 if self
._ForegroundHTdc
:
2005 hitcolor
= self
._ForegroundHTdc
.GetPixelPoint( xy
)
2007 hitcolor
= self
._HTdc
.GetPixelPoint( xy
)
2008 color
= ( hitcolor
.Red(), hitcolor
.Green(), hitcolor
.Blue() )
2009 if color
in self
.HitDict
[ HitEvent
]:
2010 Object
= self
.HitDict
[ HitEvent
][color
]
2011 ## Add the hit coords to the Object
2012 Object
.HitCoords
= self
.PixelToWorld( xy
)
2013 Object
.HitCoordsPixel
= xy
2014 Object
.CallBackFuncs
[HitEvent
](Object
)
2018 def MouseOverTest(self
, event
):
2019 ##fixme: Can this be cleaned up?
2021 xy
= event
.GetPosition()
2022 if self
._ForegroundHTdc
:
2023 hitcolor
= self
._ForegroundHTdc
.GetPixelPoint( xy
)
2025 hitcolor
= self
._HTdc
.GetPixelPoint( xy
)
2026 color
= ( hitcolor
.Red(), hitcolor
.Green(), hitcolor
.Blue() )
2027 OldObject
= self
.ObjectUnderMouse
2028 ObjectCallbackCalled
= False
2029 if color
in self
.HitDict
[ EVT_FC_ENTER_OBJECT
]:
2030 Object
= self
.HitDict
[ EVT_FC_ENTER_OBJECT
][color
]
2031 if (OldObject
is None):
2033 Object
.CallBackFuncs
[EVT_FC_ENTER_OBJECT
](Object
)
2034 ObjectCallbackCalled
= True
2036 pass # this means the enter event isn't bound for that object
2037 elif OldObject
== Object
: # the mouse is still on the same object
2039 ## Is the mouse on a differnt object as it was...
2040 elif not (Object
== OldObject
):
2041 # call the leave object callback
2043 OldObject
.CallBackFuncs
[EVT_FC_LEAVE_OBJECT
](OldObject
)
2044 ObjectCallbackCalled
= True
2046 pass # this means the leave event isn't bound for that object
2048 Object
.CallBackFuncs
[EVT_FC_ENTER_OBJECT
](Object
)
2049 ObjectCallbackCalled
= True
2051 pass # this means the enter event isn't bound for that object
2052 ## set the new object under mouse
2053 self
.ObjectUnderMouse
= Object
2054 elif color
in self
.HitDict
[ EVT_FC_LEAVE_OBJECT
]:
2055 Object
= self
.HitDict
[ EVT_FC_LEAVE_OBJECT
][color
]
2056 self
.ObjectUnderMouse
= Object
2058 # no objects under mouse bound to mouse-over events
2059 self
.ObjectUnderMouse
= None
2062 OldObject
.CallBackFuncs
[EVT_FC_LEAVE_OBJECT
](OldObject
)
2063 ObjectCallbackCalled
= True
2065 pass # this means the leave event isn't bound for that object
2066 return ObjectCallbackCalled
2069 ## fixme: There is a lot of repeated code here
2070 ## Is there a better way?
2071 def LeftDoubleClickEvent(self
,event
):
2072 if self
.GUIMode
== "Mouse":
2073 EventType
= EVT_FC_LEFT_DCLICK
2074 if not self
.HitTest(event
, EventType
):
2075 self
._RaiseMouseEvent
(event
, EventType
)
2077 def MiddleDownEvent(self
,event
):
2078 if self
.GUIMode
== "Mouse":
2079 EventType
= EVT_FC_MIDDLE_DOWN
2080 if not self
.HitTest(event
, EventType
):
2081 self
._RaiseMouseEvent
(event
, EventType
)
2083 def MiddleUpEvent(self
,event
):
2084 if self
.GUIMode
== "Mouse":
2085 EventType
= EVT_FC_MIDDLE_UP
2086 if not self
.HitTest(event
, EventType
):
2087 self
._RaiseMouseEvent
(event
, EventType
)
2089 def MiddleDoubleClickEvent(self
,event
):
2090 if self
.GUIMode
== "Mouse":
2091 EventType
= EVT_FC_MIDDLE_DCLICK
2092 if not self
.HitTest(event
, EventType
):
2093 self
._RaiseMouseEvent
(event
, EventType
)
2095 def RightUpEvent(self
,event
):
2096 if self
.GUIMode
== "Mouse":
2097 EventType
= EVT_FC_RIGHT_UP
2098 if not self
.HitTest(event
, EventType
):
2099 self
._RaiseMouseEvent
(event
, EventType
)
2101 def RightDoubleCLickEvent(self
,event
):
2102 if self
.GUIMode
== "Mouse":
2103 EventType
= EVT_FC_RIGHT_DCLICK
2104 if not self
.HitTest(event
, EventType
):
2105 self
._RaiseMouseEvent
(event
, EventType
)
2107 def WheelEvent(self
,event
):
2108 ##if self.GUIMode == "Mouse":
2109 ## Why not always raise this?
2110 self
._RaiseMouseEvent
(event
, EVT_FC_MOUSEWHEEL
)
2113 def LeftDownEvent(self
,event
):
2115 if self
.GUIMode
== "ZoomIn":
2116 self
.StartRBBox
= array( event
.GetPosition() )
2117 self
.PrevRBBox
= None
2119 elif self
.GUIMode
== "ZoomOut":
2120 Center
= self
.PixelToWorld( event
.GetPosition() )
2121 self
.Zoom(1/1.5,Center
)
2122 elif self
.GUIMode
== "Move":
2123 self
.SetCursor(self
.GrabHandCursor
)
2124 self
.StartMove
= array( event
.GetPosition() )
2125 self
.PrevMoveXY
= (0,0)
2126 elif self
.GUIMode
== "Mouse":
2128 if not self
.HitTest(event
, EVT_FC_LEFT_DOWN
):
2129 self
._RaiseMouseEvent
(event
,EVT_FC_LEFT_DOWN
)
2133 def LeftUpEvent(self
,event
):
2134 if self
.HasCapture():
2137 if self
.GUIMode
== "ZoomIn":
2138 if event
.LeftUp() and not self
.StartRBBox
is None:
2139 self
.PrevRBBox
= None
2140 EndRBBox
= event
.GetPosition()
2141 StartRBBox
= self
.StartRBBox
2142 # if mouse has moved less that ten pixels, don't use the box.
2143 if ( abs(StartRBBox
[0] - EndRBBox
[0]) > 10
2144 and abs(StartRBBox
[1] - EndRBBox
[1]) > 10 ):
2145 EndRBBox
= self
.PixelToWorld(EndRBBox
)
2146 StartRBBox
= self
.PixelToWorld(StartRBBox
)
2147 BB
= array(((min(EndRBBox
[0],StartRBBox
[0]),
2148 min(EndRBBox
[1],StartRBBox
[1])),
2149 (max(EndRBBox
[0],StartRBBox
[0]),
2150 max(EndRBBox
[1],StartRBBox
[1]))),Float
)
2153 Center
= self
.PixelToWorld(StartRBBox
)
2154 self
.Zoom(1.5,Center
)
2155 self
.StartRBBox
= None
2156 elif self
.GUIMode
== "Move":
2157 self
.SetCursor(self
.HandCursor
)
2158 if self
.StartMove
is not None:
2159 StartMove
= self
.StartMove
2160 EndMove
= array((event
.GetX(),event
.GetY()))
2161 if sum((StartMove
-EndMove
)**2) > 16:
2162 self
.MoveImage(StartMove
-EndMove
,'Pixel')
2163 self
.StartMove
= None
2164 elif self
.GUIMode
== "Mouse":
2165 EventType
= EVT_FC_LEFT_UP
2166 if not self
.HitTest(event
, EventType
):
2167 self
._RaiseMouseEvent
(event
, EventType
)
2171 def MotionEvent(self
,event
):
2173 if self
.GUIMode
== "ZoomIn":
2174 if event
.Dragging() and event
.LeftIsDown() and not (self
.StartRBBox
is None):
2175 xy0
= self
.StartRBBox
2176 xy1
= array( event
.GetPosition() )
2178 wh
[0] = max(wh
[0], int(wh
[1]*self
.AspectRatio
))
2179 wh
[1] = int(wh
[0] / self
.AspectRatio
)
2180 xy_c
= (xy0
+ xy1
) / 2
2181 dc
= wx
.ClientDC(self
)
2183 dc
.SetPen(wx
.Pen('WHITE', 2, wx
.SHORT_DASH
))
2184 dc
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2185 dc
.SetLogicalFunction(wx
.XOR
)
2187 dc
.DrawRectanglePointSize(*self
.PrevRBBox
)
2188 self
.PrevRBBox
= ( xy_c
- wh
/2, wh
)
2189 dc
.DrawRectanglePointSize( *self
.PrevRBBox
)
2191 elif self
.GUIMode
== "Move":
2192 if event
.Dragging() and event
.LeftIsDown() and not self
.StartMove
is None:
2193 xy1
= array( event
.GetPosition() )
2195 xy_tl
= xy1
- self
.StartMove
2196 dc
= wx
.ClientDC(self
)
2198 x1
,y1
= self
.PrevMoveXY
2200 w
,h
= self
.PanelSize
2201 ##fixme: This sure could be cleaner!
2202 if x2
> x1
and y2
> y1
:
2209 elif x2
> x1
and y2
<= y1
:
2218 elif x2
<= x1
and y2
> y1
:
2227 elif x2
<= x1
and y2
<= y1
:
2237 dc
.SetPen(wx
.TRANSPARENT_PEN
)
2238 dc
.SetBrush(self
.BackgroundBrush
)
2239 dc
.DrawRectangle(xa
, ya
, wa
, ha
)
2240 dc
.DrawRectangle(xb
, yb
, wb
, hb
)
2241 self
.PrevMoveXY
= xy_tl
2242 if self
._ForeDrawList
:
2243 ##if self._ForegroundBuffer:
2244 dc
.DrawBitmapPoint(self
._ForegroundBuffer
,xy_tl
)
2246 dc
.DrawBitmapPoint(self
._Buffer
,xy_tl
)
2248 elif self
.GUIMode
== "Mouse":
2249 ## Only do something if there are mouse over events bound
2250 if self
.HitDict
and (self
.HitDict
[ EVT_FC_ENTER_OBJECT
] or self
.HitDict
[ EVT_FC_LEAVE_OBJECT
] ):
2251 if not self
.MouseOverTest(event
):
2252 self
._RaiseMouseEvent
(event
,EVT_FC_MOTION
)
2255 self
._RaiseMouseEvent
(event
,EVT_FC_MOTION
)
2259 def RightDownEvent(self
,event
):
2261 if self
.GUIMode
== "ZoomIn":
2262 Center
= self
.PixelToWorld((event
.GetX(),event
.GetY()))
2263 self
.Zoom(1/1.5,Center
)
2264 elif self
.GUIMode
== "ZoomOut":
2265 Center
= self
.PixelToWorld((event
.GetX(),event
.GetY()))
2266 self
.Zoom(1.5,Center
)
2267 elif self
.GUIMode
== "Mouse":
2268 EventType
= EVT_FC_RIGHT_DOWN
2269 if not self
.HitTest(event
, EventType
):
2270 self
._RaiseMouseEvent
(event
, EventType
)
2274 def MakeNewBuffers(self
):
2275 #print "Making new buffers"
2276 self
._BackgroundDirty
= True
2277 # Make new offscreen bitmap:
2278 self
._Buffer
= wx
.EmptyBitmap(*self
.PanelSize
)
2280 #dc.SelectObject(self._Buffer)
2282 if self
._ForeDrawList
:
2283 self
._ForegroundBuffer
= wx
.EmptyBitmap(*self
.PanelSize
)
2285 self
._ForegroundBuffer
= None
2290 self
._ForegroundHTdc
= None
2292 def MakeNewHTdc(self
):
2293 ## Note: While it's considered a "bad idea" to keep a
2294 ## MemoryDC around I'm doing it here because a wx.Bitmap
2295 ## doesn't have a GetPixel method so a DC is needed to do
2296 ## the hit-test. It didn't seem like a good idea to re-create
2297 ## a wx.MemoryDC on every single mouse event, so I keep it
2299 self
._HTdc
= wx
.MemoryDC()
2300 self
._HTBitmap
= wx
.EmptyBitmap(*self
.PanelSize
)
2301 self
._HTdc
.SelectObject( self
._HTBitmap
)
2302 self
._HTdc
.SetBackground(wx
.BLACK_BRUSH
)
2303 if self
._ForeDrawList
:
2304 self
._ForegroundHTdc
= wx
.MemoryDC()
2305 self
._ForegroundHTBitmap
= wx
.EmptyBitmap(*self
.PanelSize
)
2306 self
._ForegroundHTdc
.SelectObject( self
._ForegroundHTBitmap
)
2307 self
._ForegroundHTdc
.SetBackground(wx
.BLACK_BRUSH
)
2309 self
._ForegroundHTdc
= None
2311 def OnSize(self
,event
):
2312 self
.PanelSize
= self
.GetClientSizeTuple()
2313 if self
.PanelSize
== (0,0):
2314 ## OS-X sometimes gives a Size event when the panel is size (0,0)
2315 self
.PanelSize
= (2,2)
2316 self
.PanelSize
= array(self
.PanelSize
, Int32
)
2317 self
.HalfPanelSize
= self
.PanelSize
/ 2 # lrk: added for speed in WorldToPixel
2318 if self
.PanelSize
[0] == 0 or self
.PanelSize
[1] == 0:
2319 self
.AspectRatio
= 1.0
2321 self
.AspectRatio
= float(self
.PanelSize
[0]) / self
.PanelSize
[1]
2322 self
.MakeNewBuffers()
2325 def OnPaint(self
, event
):
2326 dc
= wx
.PaintDC(self
)
2327 if self
._ForegroundBuffer
:
2328 dc
.DrawBitmap(self
._ForegroundBuffer
,0,0)
2330 dc
.DrawBitmap(self
._Buffer
,0,0)
2332 def Draw(self
, Force
=False):
2334 There is a main buffer set up to double buffer the screen, so
2335 you can get quick re-draws when the window gets uncovered.
2337 If there are any objects in self._ForeDrawList, then the
2338 background gets drawn to a new buffer, and the foreground
2339 objects get drawn on top of it. The final result if blitted to
2340 the screen, and stored for future Paint events. This is done so
2341 that you can have a complicated background, but have something
2342 changing on the foreground, without having to wait for the
2343 background to get re-drawn. This can be used to support simple
2344 animation, for instance.
2347 if sometrue(self
.PanelSize
< 1 ): # it's possible for this to get called before being properly initialized.
2349 if self
.Debug
: start
= clock()
2350 ScreenDC
= wx
.ClientDC(self
)
2351 ViewPortWorld
= ( self
.PixelToWorld((0,0)),
2352 self
.PixelToWorld(self
.PanelSize
) )
2353 ViewPortBB
= array( ( minimum
.reduce(ViewPortWorld
),
2354 maximum
.reduce(ViewPortWorld
) ) )
2356 dc
.SelectObject(self
._Buffer
)
2357 if self
._BackgroundDirty
or Force
:
2358 #print "Background is Dirty"
2359 dc
.SetBackground(self
.BackgroundBrush
)
2363 self
._DrawObjects
(dc
, self
._DrawList
, ScreenDC
, ViewPortBB
, self
._HTdc
)
2364 self
._BackgroundDirty
= False
2366 if self
._ForeDrawList
:
2367 ## If an object was just added to the Foreground, there might not yet be a buffer
2368 if self
._ForegroundBuffer
is None:
2369 self
._ForegroundBuffer
= wx
.EmptyBitmap(self
.PanelSize
[0],
2372 dc
= wx
.MemoryDC() ## I got some strange errors (linewidths wrong) if I didn't make a new DC here
2373 dc
.SelectObject(self
._ForegroundBuffer
)
2374 dc
.DrawBitmap(self
._Buffer
,0,0)
2375 if self
._ForegroundHTdc
is None:
2376 self
._ForegroundHTdc
= wx
.MemoryDC()
2377 self
._ForegroundHTdc
.SelectObject( wx
.EmptyBitmap(
2379 self
.PanelSize
[1]) )
2381 ## blit the background HT buffer to the foreground HT buffer
2382 self
._ForegroundHTdc
.Blit(0, 0,
2383 self
.PanelSize
[0], self
.PanelSize
[1],
2385 self
._DrawObjects
(dc
,
2389 self
._ForegroundHTdc
)
2390 ScreenDC
.Blit(0, 0, self
.PanelSize
[0],self
.PanelSize
[1], dc
, 0, 0)
2391 # If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn
2392 # This seeems out of place, but it works.
2394 ScreenDC
.SetPen(wx
.Pen('WHITE', 2,wx
.SHORT_DASH
))
2395 ScreenDC
.SetBrush(wx
.TRANSPARENT_BRUSH
)
2396 ScreenDC
.SetLogicalFunction(wx
.XOR
)
2397 ScreenDC
.DrawRectanglePointSize(*self
.PrevRBBox
)
2398 if self
.Debug
: print "Drawing took %f seconds of CPU time"%(clock()-start
)
2400 ## Clear the font cache
2401 ## IF you don't do this, the X font server starts to take up Massive amounts of memory
2402 ## This is mostly a problem with very large fonts, that you get with scaled text when zoomed in.
2403 DrawObject
.FontList
= {}
2405 def _ShouldRedraw(DrawList
, ViewPortBB
): # lrk: adapted code from BBCheck
2406 # lrk: Returns the objects that should be redrawn
2410 for Object
in DrawList
:
2411 BB1
= Object
.BoundingBox
2412 if (BB1
[1,0] > BB2
[0,0] and BB1
[0,0] < BB2
[1,0] and
2413 BB1
[1,1] > BB2
[0,1] and BB1
[0,1] < BB2
[1,1]):
2414 redrawlist
.append(Object
)
2416 _ShouldRedraw
= staticmethod(_ShouldRedraw
)
2419 ## def BBCheck(self, BB1, BB2):
2422 ## BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise
2425 ## if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and
2426 ## (BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ):
2431 def MoveImage(self
,shift
,CoordType
):
2433 move the image in the window.
2435 shift is an (x,y) tuple, specifying the amount to shift in each direction
2437 It can be in any of three coordinates: Panel, Pixel, World,
2438 specified by the CoordType parameter
2440 Panel coordinates means you want to shift the image by some
2441 fraction of the size of the displaed image
2443 Pixel coordinates means you want to shift the image by some number of pixels
2445 World coordinates mean you want to shift the image by an amount
2446 in Floating point world coordinates
2450 shift
= asarray(shift
,Float
)
2451 #print "shifting by:", shift
2452 if CoordType
== 'Panel':# convert from panel coordinates
2453 shift
= shift
* array((-1,1),Float
) *self
.PanelSize
/self
.TransformVector
2454 elif CoordType
== 'Pixel': # convert from pixel coordinates
2455 shift
= shift
/self
.TransformVector
2456 elif CoordType
== 'World': # No conversion
2459 raise FloatCanvasError('CoordType must be either "Panel", "Pixel", or "World"')
2461 #print "shifting by:", shift
2463 self
.ViewPortCenter
= self
.ViewPortCenter
+ shift
2464 self
.MapProjectionVector
= self
.ProjectionFun(self
.ViewPortCenter
)
2465 self
.TransformVector
= array((self
.Scale
,-self
.Scale
),Float
) * self
.MapProjectionVector
2466 self
._BackgroundDirty
= True
2469 def Zoom(self
,factor
,center
= None):
2472 Zoom(factor, center) changes the amount of zoom of the image by factor.
2473 If factor is greater than one, the image gets larger.
2474 If factor is less than one, the image gets smaller.
2476 Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming.
2477 If center is not given, the center will stay the same.
2480 self
.Scale
= self
.Scale
*factor
2481 if not center
is None:
2482 self
.ViewPortCenter
= array(center
,Float
)
2483 self
.MapProjectionVector
= self
.ProjectionFun(self
.ViewPortCenter
)
2484 self
.TransformVector
= array((self
.Scale
,-self
.Scale
),Float
) * self
.MapProjectionVector
2485 self
._BackgroundDirty
= True
2488 def ZoomToBB(self
, NewBB
= None, DrawFlag
= True):
2492 Zooms the image to the bounding box given, or to the bounding
2493 box of all the objects on the canvas, if none is given.
2497 if not NewBB
is None:
2500 if self
.BoundingBoxDirty
:
2501 self
._ResetBoundingBox
()
2502 BoundingBox
= self
.BoundingBox
2503 if not BoundingBox
is None:
2504 self
.ViewPortCenter
= array(((BoundingBox
[0,0]+BoundingBox
[1,0])/2,
2505 (BoundingBox
[0,1]+BoundingBox
[1,1])/2 ),Float
)
2506 self
.MapProjectionVector
= self
.ProjectionFun(self
.ViewPortCenter
)
2507 # Compute the new Scale
2508 BoundingBox
= BoundingBox
* self
.MapProjectionVector
2510 self
.Scale
= min(abs(self
.PanelSize
[0] / (BoundingBox
[1,0]-BoundingBox
[0,0])),
2511 abs(self
.PanelSize
[1] / (BoundingBox
[1,1]-BoundingBox
[0,1])) )*0.95
2512 except ZeroDivisionError: # this will happen if the BB has zero width or height
2514 self
.Scale
= (self
.PanelSize
[0] / (BoundingBox
[1,0]-BoundingBox
[0,0]))*0.95
2515 except ZeroDivisionError:
2517 self
.Scale
= (self
.PanelSize
[1] / (BoundingBox
[1,1]-BoundingBox
[0,1]))*0.95
2518 except ZeroDivisionError: #zero size! (must be a single point)
2521 self
.TransformVector
= array((self
.Scale
,-self
.Scale
),Float
)* self
.MapProjectionVector
2523 self
._BackgroundDirty
= True
2526 # Reset the shifting and scaling to defaults when there is no BB
2527 self
.ViewPortCenter
= array( (0,0), Float
)
2528 self
.MapProjectionVector
= array( (1,1), Float
) # No Projection to start!
2529 self
.TransformVector
= array( (1,-1), Float
) # default Transformation
2532 def RemoveObjects(self
, Objects
):
2533 for Object
in Objects
:
2534 self
.RemoveObject(Object
, ResetBB
= False)
2535 self
.BoundingBoxDirty
= True
2537 def RemoveObject(self
, Object
, ResetBB
= True):
2538 ##fixme: Using the list.remove method is kind of slow
2539 if Object
.InForeground
:
2540 self
._ForeDrawList
.remove(Object
)
2541 if not self
._ForeDrawList
:
2542 self
._ForegroundBuffer
= None
2543 self
._ForegroundHTdc
= None
2545 self
._DrawList
.remove(Object
)
2546 self
._BackgroundDirty
= True
2548 self
.BoundingBoxDirty
= True
2550 def ClearAll(self
, ResetBB
= True):
2552 self
._ForeDrawList
= []
2553 self
._BackgroundDirty
= True
2554 self
.HitColorGenerator
= None
2555 self
.UseHitTest
= False
2557 self
._ResetBoundingBox
()
2558 self
.MakeNewBuffers()
2562 ## def _AddBoundingBox(self,NewBB):
2563 ## if self.BoundingBox is None:
2564 ## self.BoundingBox = NewBB
2565 ## self.ZoomToBB(NewBB,DrawFlag = False)
2567 ## self.BoundingBox = array( ( (min(self.BoundingBox[0,0],NewBB[0,0]),
2568 ## min(self.BoundingBox[0,1],NewBB[0,1])),
2569 ## (max(self.BoundingBox[1,0],NewBB[1,0]),
2570 ## max(self.BoundingBox[1,1],NewBB[1,1]))),
2573 def _getboundingbox(bboxarray
): # lrk: added this
2575 upperleft
= minimum
.reduce(bboxarray
[:,0])
2576 lowerright
= maximum
.reduce(bboxarray
[:,1])
2577 return array((upperleft
, lowerright
), Float
)
2579 _getboundingbox
= staticmethod(_getboundingbox
)
2581 def _ResetBoundingBox(self
):
2582 if self
._DrawList
or self
._ForeDrawList
:
2583 bboxarray
= zeros((len(self
._DrawList
)+len(self
._ForeDrawList
), 2, 2),Float
)
2584 i
= -1 # just in case _DrawList is empty
2585 for (i
, BB
) in enumerate(self
._DrawList
):
2586 bboxarray
[i
] = BB
.BoundingBox
2587 for (j
, BB
) in enumerate(self
._ForeDrawList
):
2588 bboxarray
[i
+j
+1] = BB
.BoundingBox
2589 self
.BoundingBox
= self
._getboundingbox
(bboxarray
)
2591 self
.BoundingBox
= None
2592 self
.ViewPortCenter
= array( (0,0), Float
)
2593 self
.TransformVector
= array( (1,-1), Float
)
2594 self
.MapProjectionVector
= array( (1,1), Float
)
2596 self
.BoundingBoxDirty
= False
2598 def PixelToWorld(self
,Points
):
2600 Converts coordinates from Pixel coordinates to world coordinates.
2602 Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates.
2605 return (((asarray(Points
,Float
) - (self
.PanelSize
/2))/self
.TransformVector
) + self
.ViewPortCenter
)
2607 def WorldToPixel(self
,Coordinates
):
2609 This function will get passed to the drawing functions of the objects,
2610 to transform from world to pixel coordinates.
2611 Coordinates should be a NX2 array of (x,y) coordinates, or
2612 a 2-tuple, or sequence of 2-tuples.
2614 #Note: this can be called by users code for various reasons, so asarray is needed.
2615 return (((asarray(Coordinates
,Float
) -
2616 self
.ViewPortCenter
)*self
.TransformVector
)+
2617 (self
.HalfPanelSize
)).astype('i')
2619 def ScaleWorldToPixel(self
,Lengths
):
2621 This function will get passed to the drawing functions of the objects,
2622 to Change a length from world to pixel coordinates.
2624 Lengths should be a NX2 array of (x,y) coordinates, or
2625 a 2-tuple, or sequence of 2-tuples.
2627 return ( (asarray(Lengths
,Float
)*self
.TransformVector
) ).astype('i')
2629 def ScalePixelToWorld(self
,Lengths
):
2631 This function computes a pair of x.y lengths,
2632 to change then from pixel to world coordinates.
2634 Lengths should be a NX2 array of (x,y) coordinates, or
2635 a 2-tuple, or sequence of 2-tuples.
2638 return (asarray(Lengths
,Float
) / self
.TransformVector
)
2640 def AddObject(self
,obj
):
2641 # put in a reference to the Canvas, so remove and other stuff can work
2643 if obj
.InForeground
:
2644 self
._ForeDrawList
.append(obj
)
2645 self
.UseForeground
= True
2647 self
._DrawList
.append(obj
)
2648 self
._BackgroundDirty
= True
2649 self
.BoundingBoxDirty
= True
2652 def _DrawObjects(self
, dc
, DrawList
, ScreenDC
, ViewPortBB
, HTdc
= None):
2654 This is a convenience function;
2655 This function takes the list of objects and draws them to specified
2658 dc
.SetBackground(self
.BackgroundBrush
)
2661 PanelSize0
, PanelSize1
= self
.PanelSize
# for speed
2662 WorldToPixel
= self
.WorldToPixel
# for speed
2663 ScaleWorldToPixel
= self
.ScaleWorldToPixel
# for speed
2664 Blit
= ScreenDC
.Blit
# for speed
2665 NumBetweenBlits
= self
.NumBetweenBlits
# for speed
2666 for i
, Object
in enumerate(self
._ShouldRedraw
(DrawList
, ViewPortBB
)):
2668 Object
._Draw
(dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
)
2669 if (i
+1) % NumBetweenBlits
== 0:
2670 Blit(0, 0, PanelSize0
, PanelSize1
, dc
, 0, 0)
2673 def SaveAsImage(self
, filename
, ImageType
=wx
.BITMAP_TYPE_PNG
):
2676 Saves the current image as an image file. The default is in the
2677 PNG format. Other formats can be spcified using the wx flags:
2682 etc. (see the wx docs for the complete list)
2686 self
._Buffer
.SaveFile(filename
, ImageType
)
2689 def _makeFloatCanvasAddMethods(): ## lrk's code for doing this in module __init__
2690 classnames
= ["Circle", "Ellipse", "Rectangle", "ScaledText", "Polygon",
2691 "Line", "Text", "PointSet","Point", "Arrow","ScaledTextBox",
2692 "SquarePoint","Bitmap", "ScaledBitmap"]
2693 for classname
in classnames
:
2694 klass
= globals()[classname
]
2695 def getaddshapemethod(klass
=klass
):
2696 def addshape(self
, *args
, **kwargs
):
2697 Object
= klass(*args
, **kwargs
)
2698 self
.AddObject(Object
)
2701 addshapemethod
= getaddshapemethod()
2702 methodname
= "Add" + classname
2703 setattr(FloatCanvas
, methodname
, addshapemethod
)
2704 docstring
= "Creates %s and adds its reference to the canvas.\n" % classname
2705 docstring
+= "Argument protocol same as %s class" % classname
2707 docstring
+= ", whose docstring is:\n%s" % klass
.__doc
__
2708 FloatCanvas
.__dict
__[methodname
].__doc
__ = docstring
2710 _makeFloatCanvasAddMethods()