]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/floatcanvas/FloatCanvas.py
4ff0fcb513d52813e8c52226cf74335cdc604eca
3 from Numeric
import array
, asarray
, Float
, cos
, pi
, sum , minimum
, maximum
, Int32
, zeros
5 from numarray
import array
, asarray
, Float
, cos
, pi
, sum , minimum
, maximum
, Int32
, zeros
7 from time
import clock
, sleep
16 ## A global variable to hold the Pixels per inch that wxWindows thinks is in use
17 ## This is used for scaling fonts.
18 ## This can't be computed on module __init__, because a wx.App might not have iniitalized yet.
21 ## a custom Exceptions:
23 class FloatCanvasException ( Exception ):
26 ## All the mouse events
27 #EVT_FC_ENTER_WINDOW = wx.NewEventType()
28 #EVT_FC_LEAVE_WINDOW = wx.NewEventType()
29 EVT_FC_LEFT_DOWN
= wx
. NewEventType ()
30 EVT_FC_LEFT_UP
= wx
. NewEventType ()
31 EVT_FC_LEFT_DCLICK
= wx
. NewEventType ()
32 EVT_FC_MIDDLE_DOWN
= wx
. NewEventType ()
33 EVT_FC_MIDDLE_UP
= wx
. NewEventType ()
34 EVT_FC_MIDDLE_DCLICK
= wx
. NewEventType ()
35 EVT_FC_RIGHT_DOWN
= wx
. NewEventType ()
36 EVT_FC_RIGHT_UP
= wx
. NewEventType ()
37 EVT_FC_RIGHT_DCLICK
= wx
. NewEventType ()
38 EVT_FC_MOTION
= wx
. NewEventType ()
39 EVT_FC_MOUSEWHEEL
= wx
. NewEventType ()
40 ## these two are for the hit-test stuff, I never make them real Events
41 EVT_FC_ENTER_OBJECT
= wx
. NewEventType ()
42 EVT_FC_LEAVE_OBJECT
= wx
. NewEventType ()
44 #def EVT_ENTER_WINDOW( window, function ):
45 # window.Connect( -1, -1, EVT_FC_ENTER_WINDOW, function )
46 #def EVT_LEAVE_WINDOW( window, function ):
47 # window.Connect( -1, -1,EVT_FC_LEAVE_WINDOW , function )
48 def EVT_LEFT_DOWN ( window
, function
):
49 window
. Connect ( - 1 , - 1 , EVT_FC_LEFT_DOWN
, function
)
50 def EVT_LEFT_UP ( window
, function
):
51 window
. Connect ( - 1 , - 1 , EVT_FC_LEFT_UP
, function
)
52 def EVT_LEFT_DCLICK ( window
, function
):
53 window
. Connect ( - 1 , - 1 , EVT_FC_LEFT_DCLICK
, function
)
54 def EVT_MIDDLE_DOWN ( window
, function
):
55 window
. Connect ( - 1 , - 1 , EVT_FC_MIDDLE_DOWN
, function
)
56 def EVT_MIDDLE_UP ( window
, function
):
57 window
. Connect ( - 1 , - 1 , EVT_FC_MIDDLE_UP
, function
)
58 def EVT_MIDDLE_DCLICK ( window
, function
):
59 window
. Connect ( - 1 , - 1 , EVT_FC_MIDDLE_DCLICK
, function
)
60 def EVT_RIGHT_DOWN ( window
, function
):
61 window
. Connect ( - 1 , - 1 , EVT_FC_RIGHT_DOWN
, function
)
62 def EVT_RIGHT_UP ( window
, function
):
63 window
. Connect ( - 1 , - 1 , EVT_FC_RIGHT_UP
, function
)
64 def EVT_RIGHT_DCLICK ( window
, function
):
65 window
. Connect ( - 1 , - 1 , EVT_FC_RIGHT_DCLICK
, function
)
66 def EVT_MOTION ( window
, function
):
67 window
. Connect ( - 1 , - 1 , EVT_FC_MOTION
, function
)
68 def EVT_MOUSEWHEEL ( window
, function
):
69 window
. Connect ( - 1 , - 1 , EVT_FC_MOUSEWHEEL
, function
)
71 class MouseEvent ( wx
. PyCommandEvent
):
75 This event class takes a regular wxWindows mouse event as a parameter,
76 and wraps it so that there is access to all the original methods. This
77 is similar to subclassing, but you can't subclass a wxWindows event
79 The goal is to be able to it just like a regular mouse event.
83 GetCoords() , which returns and (x,y) tuple in world coordinates.
85 Another differnce is that it is a CommandEvent, which propagates up
86 the window hierarchy until it is handled.
90 def __init__ ( self
, EventType
, NativeEvent
, WinID
, Coords
= None ):
91 wx
. PyCommandEvent
.__ init
__ ( self
)
93 self
. SetEventType ( EventType
)
94 self
._ NativeEvent
= NativeEvent
97 def SetCoords ( self
, Coords
):
103 def __getattr__ ( self
, name
):
104 #return eval(self.NativeEvent.__getattr__(name) )
105 return getattr ( self
._ NativeEvent
, name
)
107 #### ColorGEnerator class is now obsolete. I'm using a python generator function instead.
108 ##class ColorGenerator:
112 ## An instance of this class generates a unique color each time
113 ## GetNextColor() is called. Someday I will use a proper Python
114 ## generator for this class.
116 ## The point of this generator is for the hit-test bitmap, each object
117 ## needs to be a unique color. Also, each system can be running a
118 ## different number of colors, and it doesn't appear to be possible to
119 ## have a wxMemDC with a different colordepth as the screen so this
120 ## generates colors far enough apart that they can be distinguished on
121 ## a 16bit screen. Anything less than 16bits won't work. It could, but
122 ## I havn't written the code that way. You also wouldn't get many
127 ## def __init__(self):
129 ## ## figure out the color depth of the screen
130 ## ## for some bizare reason, thisdoesn't work on OS-X
131 ## if sys.platform == 'darwin':
134 ## b = wx.EmptyBitmap(1,1)
135 ## depth = b.GetDepth()
144 ## raise FloatCanvasException("ColorGenerator does not work with depth = %s"%depth )
146 ## def GetNextColor(self):
148 ## ##r,g,b = self.r,self.g,self.b
157 ## ## fixme: this should be a derived exception
158 ## raise FloatCanvasException("Too many objects in colorgenerator for HitTest")
159 ## return (self.r,self.g,self.b)
166 def cycleidxs ( indexcount
, maxvalue
, step
):
170 for idx
in xrange ( 0 , maxvalue
, step
):
171 for tail
in cycleidxs ( indexcount
- 1 , maxvalue
, step
):
174 def colorGenerator ():
176 if sys
. platform
== 'darwin' :
179 b
= wx
. EmptyBitmap ( 1 , 1 )
186 raise "ColorGenerator does not work with depth = %s " % depth
187 return cycleidxs ( indexcount
= 3 , maxvalue
= 256 , step
= step
)
190 #### I don't know if the Set objects are useful, beyond the pointset object
191 #### The problem is that when zoomed in, the BB is checked to see whether to draw the object.
192 #### A Set object can defeat this
194 ##class ObjectSetMixin:
196 ## A mix-in class for draw objects that are sets of objects
198 ## It contains methods for setting lists of pens and brushes
201 ## def SetPens(self,LineColors,LineStyles,LineWidths):
203 ## This method used when an object could have a list of pens, rather than just one
204 ## It is used for LineSet, and perhaps others in the future.
206 ## fixme: this should be in a mixin
208 ## fixme: this is really kludgy, there has got to be a better way!
213 ## if type(LineColors) == types.ListType:
214 ## length = len(LineColors)
216 ## LineColors = [LineColors]
218 ## if type(LineStyles) == types.ListType:
219 ## length = len(LineStyles)
221 ## LineStyles = [LineStyles]
223 ## if type(LineWidths) == types.ListType:
224 ## length = len(LineWidths)
226 ## LineWidths = [LineWidths]
229 ## if len(LineColors) == 1:
230 ## LineColors = LineColors*length
231 ## if len(LineStyles) == 1:
232 ## LineStyles = LineStyles*length
233 ## if len(LineWidths) == 1:
234 ## LineWidths = LineWidths*length
237 ## for (LineColor,LineStyle,LineWidth) in zip(LineColors,LineStyles,LineWidths):
238 ## if LineColor is None or LineStyle is None:
239 ## self.Pens.append(wx.TRANSPARENT_PEN)
240 ## # what's this for?> self.LineStyle = 'Transparent'
241 ## if not self.PenList.has_key((LineColor,LineStyle,LineWidth)):
242 ## Pen = wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle])
243 ## self.Pens.append(Pen)
245 ## self.Pens.append(self.PenList[(LineColor,LineStyle,LineWidth)])
247 ## self.Pens = self.Pens[0]
253 This is the base class for all the objects that can be drawn.
257 def __init__ ( self
, InForeground
= False ):
258 self
. InForeground
= InForeground
263 self
. CallBackFuncs
= {}
265 ## these are the defaults
269 self
. MinHitLineWidth
= 3
270 self
. HitLineWidth
= 3 ## this gets re-set by the subclasses if necessary
272 # I pre-define all these as class variables to provide an easier
273 # interface, and perhaps speed things up by caching all the Pens
274 # and Brushes, although that may not help, as I think wx now
275 # does that on it's own. Send me a note if you know!
278 ( None , "Transparent" ) : wx
. TRANSPARENT_BRUSH
,
279 ( "Blue" , "Solid" ) : wx
. BLUE_BRUSH
,
280 ( "Green" , "Solid" ) : wx
. GREEN_BRUSH
,
281 ( "White" , "Solid" ) : wx
. WHITE_BRUSH
,
282 ( "Black" , "Solid" ) : wx
. BLACK_BRUSH
,
283 ( "Grey" , "Solid" ) : wx
. GREY_BRUSH
,
284 ( "MediumGrey" , "Solid" ) : wx
. MEDIUM_GREY_BRUSH
,
285 ( "LightGrey" , "Solid" ) : wx
. LIGHT_GREY_BRUSH
,
286 ( "Cyan" , "Solid" ) : wx
. CYAN_BRUSH
,
287 ( "Red" , "Solid" ) : wx
. RED_BRUSH
290 ( None , "Transparent" , 1 ) : wx
. TRANSPARENT_PEN
,
291 ( "Green" , "Solid" , 1 ) : wx
. GREEN_PEN
,
292 ( "White" , "Solid" , 1 ) : wx
. WHITE_PEN
,
293 ( "Black" , "Solid" , 1 ) : wx
. BLACK_PEN
,
294 ( "Grey" , "Solid" , 1 ) : wx
. GREY_PEN
,
295 ( "MediumGrey" , "Solid" , 1 ) : wx
. MEDIUM_GREY_PEN
,
296 ( "LightGrey" , "Solid" , 1 ) : wx
. LIGHT_GREY_PEN
,
297 ( "Cyan" , "Solid" , 1 ) : wx
. CYAN_PEN
,
298 ( "Red" , "Solid" , 1 ) : wx
. RED_PEN
302 "Transparent" : wx
. TRANSPARENT
,
304 "BiDiagonalHatch" : wx
. BDIAGONAL_HATCH
,
305 "CrossDiagHatch" : wx
. CROSSDIAG_HATCH
,
306 "FDiagonal_Hatch" : wx
. FDIAGONAL_HATCH
,
307 "CrossHatch" : wx
. CROSS_HATCH
,
308 "HorizontalHatch" : wx
. HORIZONTAL_HATCH
,
309 "VerticalHatch" : wx
. VERTICAL_HATCH
314 "Transparent" : wx
. TRANSPARENT
,
316 "LongDash" : wx
. LONG_DASH
,
317 "ShortDash" : wx
. SHORT_DASH
,
318 "DotDash" : wx
. DOT_DASH
,
321 def Bind ( self
, Event
, CallBackFun
):
322 self
. CallBackFuncs
[ Event
] = CallBackFun
324 self
._ Canvas
. UseHitTest
= True
325 if not self
._ Canvas
._ HTdc
:
326 self
._ Canvas
. MakeNewHTdc ()
327 if not self
. HitColor
:
328 if not self
._ Canvas
. HitColorGenerator
:
329 self
._ Canvas
. HitColorGenerator
= colorGenerator ()
330 self
._ Canvas
. HitColorGenerator
. next () # first call to prevent the background color from being used.
331 self
. HitColor
= self
._ Canvas
. HitColorGenerator
. next ()
332 self
. SetHitPen ( self
. HitColor
, self
. HitLineWidth
)
333 self
. SetHitBrush ( self
. HitColor
)
334 # put the object in the hit dict, indexed by it's color
335 if not self
._ Canvas
. HitDict
:
336 self
._ Canvas
. MakeHitDict ()
337 self
._ Canvas
. HitDict
[ Event
][ self
. HitColor
] = ( self
) # put the object in the hit dict, indexed by it's color
341 ## fixme: this only removes one from each list, there could be more.
342 if self
._ Canvas
. HitDict
:
343 for List
in self
._ Canvas
. HitDict
. itervalues ():
350 def SetBrush ( self
, FillColor
, FillStyle
):
351 if FillColor
is None or FillStyle
is None :
352 self
. Brush
= wx
. TRANSPARENT_BRUSH
353 self
. FillStyle
= "Transparent"
355 self
. Brush
= self
. BrushList
. setdefault ( ( FillColor
, FillStyle
), wx
. Brush ( FillColor
, self
. FillStyleList
[ FillStyle
] ) )
357 def SetPen ( self
, LineColor
, LineStyle
, LineWidth
):
358 if ( LineColor
is None ) or ( LineStyle
is None ):
359 self
. Pen
= wx
. TRANSPARENT_PEN
360 self
. LineStyle
= 'Transparent'
362 self
. Pen
= self
. PenList
. setdefault ( ( LineColor
, LineStyle
, LineWidth
), wx
. Pen ( LineColor
, LineWidth
, self
. LineStyleList
[ LineStyle
]) )
364 def SetHitBrush ( self
, HitColor
):
366 self
. HitBrush
= wx
. TRANSPARENT_BRUSH
368 self
. HitBrush
= self
. BrushList
. setdefault ( ( HitColor
, "solid" ), wx
. Brush ( HitColor
, self
. FillStyleList
[ "Solid" ] ) )
370 def SetHitPen ( self
, HitColor
, LineWidth
):
372 self
. HitPen
= wx
. TRANSPARENT_PEN
374 self
. HitPen
= self
. PenList
. setdefault ( ( HitColor
, "solid" , LineWidth
), wx
. Pen ( HitColor
, LineWidth
, self
. LineStyleList
[ "Solid" ]) )
376 def PutInBackground ( self
):
377 if self
._ Canvas
and self
. InForeground
:
378 self
._ Canvas
._ ForeDrawList
. remove ( self
)
379 self
._ Canvas
._ DrawList
. append ( self
)
380 self
._ Canvas
._ BackgroundDirty
= True
381 self
. InForeground
= False
383 def PutInForeground ( self
):
384 if self
._ Canvas
and ( not self
. InForeground
):
385 self
._ Canvas
._ ForeDrawList
. append ( self
)
386 self
._ Canvas
._ DrawList
. remove ( self
)
387 self
._ Canvas
._ BackgroundDirty
= True
388 self
. InForeground
= True
393 This is a mixin class that provides some methods suitable for use
394 with objects that have a single (x,y) coordinate pair.
398 def Move ( self
, Delta
):
401 Move(Delta): moves the object by delta, where delta is a
402 (dx,dy) pair. Ideally a Numpy array of shape (2,)
406 Delta
= asarray ( Delta
, Float
)
408 self
. BoundingBox
= self
. BoundingBox
+ Delta
410 self
._ Canvas
. BoundingBoxDirty
= True
412 class PointsObjectMixin
:
415 This is a mixin class that provides some methods suitable for use
416 with objects that have a set of (x,y) coordinate pairs.
420 ## This is code for the XYMixin object, it needs to be adapeted and tested.
421 ## def Move(self, Delta ):
424 ## Move(Delta): moves the object by delta, where delta is an (dx,
425 ## dy) pair. Ideally a Numpy array or shape (2,)
429 ## Delta = array(Delta, Float)
431 ## self.BoundingBox = self.BoundingBox + Delta##array((self.XY, (self.XY + self.WH)), Float)
433 ## self._Canvas.BoundingBoxDirty = True
435 def SetPoints ( self
, Points
):
437 self
. BoundingBox
= array ((( min ( self
. Points
[:, 0 ]), min ( self
. Points
[:, 1 ])),( max ( self
. Points
[:, 0 ]), max ( self
. Points
[:, 1 ]))), Float
)
439 self
._ Canvas
. BoundingBoxDirty
= True
443 class Polygon ( DrawObject
, PointsObjectMixin
):
447 The Polygon class takes a list of 2-tuples, or a NX2 NumPy array of
448 point coordinates. so that Points[N][0] is the x-coordinate of
449 point N and Points[N][1] is the y-coordinate or Points[N,0] is the
450 x-coordinate of point N and Points[N,1] is the y-coordinate for
461 InForeground
= False ):
462 DrawObject
.__ init
__ ( self
, InForeground
)
463 self
. Points
= array ( Points
, Float
) # this DOES need to make a copy
464 self
. BoundingBox
= array ((( min ( self
. Points
[:, 0 ]), min ( self
. Points
[:, 1 ])),( max ( self
. Points
[:, 0 ]), max ( self
. Points
[:, 1 ]))), Float
)
466 self
. LineColor
= LineColor
467 self
. LineStyle
= LineStyle
468 self
. LineWidth
= LineWidth
469 self
. FillColor
= FillColor
470 self
. FillStyle
= FillStyle
472 self
. HitLineWidth
= max ( LineWidth
, self
. MinHitLineWidth
)
474 self
. SetPen ( LineColor
, LineStyle
, LineWidth
)
475 self
. SetBrush ( FillColor
, FillStyle
)
477 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
= None , HTdc
= None ):
478 Points
= WorldToPixel ( self
. Points
)
480 dc
. SetBrush ( self
. Brush
)
481 dc
. DrawPolygon ( Points
)
482 if HTdc
and self
. HitAble
:
483 HTdc
. SetPen ( self
. HitPen
)
484 HTdc
. SetBrush ( self
. HitBrush
)
485 HTdc
. DrawPolygon ( Points
)
487 ##class PolygonSet(DrawObject):
489 ## The PolygonSet class takes a Geometry.Polygon object.
490 ## so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
492 ## it creates a set of line segments, from (x1,y1) to (x2,y2)
496 ## def __init__(self,PolySet,LineColors,LineStyles,LineWidths,FillColors,FillStyles,InForeground = False):
497 ## DrawObject.__init__(self, InForeground)
499 ## ##fixme: there should be some error checking for everything being the right length.
502 ## self.Points = array(Points,Float)
503 ## self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
505 ## self.LineColors = LineColors
506 ## self.LineStyles = LineStyles
507 ## self.LineWidths = LineWidths
508 ## self.FillColors = FillColors
509 ## self.FillStyles = FillStyles
511 ## self.SetPens(LineColors,LineStyles,LineWidths)
513 ## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
514 ## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
515 ## Points = WorldToPixel(self.Points)
516 ## Points.shape = (-1,4)
517 ## dc.DrawLineList(Points,self.Pens)
520 class Line ( DrawObject
, PointsObjectMixin
):
522 The Line class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
523 so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate
524 or Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays.
526 It will draw a straight line if there are two points, and a polyline if there are more than two.
529 def __init__ ( self
, Points
,
533 InForeground
= False ):
534 DrawObject
.__ init
__ ( self
, InForeground
)
537 self
. Points
= array ( Points
, Float
)
538 self
. BoundingBox
= array ((( min ( self
. Points
[:, 0 ]), min ( self
. Points
[:, 1 ])),( max ( self
. Points
[:, 0 ]), max ( self
. Points
[:, 1 ]))), Float
)
540 self
. LineColor
= LineColor
541 self
. LineStyle
= LineStyle
542 self
. LineWidth
= LineWidth
544 self
. SetPen ( LineColor
, LineStyle
, LineWidth
)
546 self
. HitLineWidth
= max ( LineWidth
, self
. MinHitLineWidth
)
549 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
550 Points
= WorldToPixel ( self
. Points
)
553 if HTdc
and self
. HitAble
:
554 HTdc
. SetPen ( self
. HitPen
)
555 HTdc
. DrawLines ( Points
)
557 ##class LineSet(DrawObject, ObjectSetMixin):
559 ## The LineSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
560 ## so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
562 ## it creates a set of line segments, from (x1,y1) to (x2,y2)
566 ## def __init__(self,Points,LineColors,LineStyles,LineWidths,InForeground = False):
567 ## DrawObject.__init__(self, InForeground)
569 ## NumLines = len(Points) / 2
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
580 ## self.SetPens(LineColors,LineStyles,LineWidths)
582 ## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
583 ## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
584 ## Points = WorldToPixel(self.Points)
585 ## Points.shape = (-1,4)
586 ## dc.DrawLineList(Points,self.Pens)
588 class PointSet ( DrawObject
):
590 The PointSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
591 so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate
592 or Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays.
594 Each point will be drawn the same color and Diameter. The Diameter is in screen points,
595 not world coordinates.
597 At this point, the hit-test code does not distingish between the
598 points, you will only know that one of the poins got hit, not which
601 In the case of points, the HitLineWidth is used as diameter.
604 def __init__ ( self
, Points
, Color
= "Black" , Diameter
= 1 , InForeground
= False ):
605 DrawObject
.__ init
__ ( self
, InForeground
)
607 self
. Points
= array ( Points
, Float
)
608 self
. Points
. shape
= (- 1 , 2 ) # Make sure it is a NX2 array, even if there is only one point
609 self
. BoundingBox
= array ((( min ( self
. Points
[:, 0 ]),
610 min ( self
. Points
[:, 1 ])),
611 ( max ( self
. Points
[:, 0 ]),
612 max ( self
. Points
[:, 1 ]))), Float
)
615 self
. Diameter
= Diameter
617 self
. HitLineWidth
= self
. MinHitLineWidth
618 self
. SetPen ( Color
, "Solid" , 1 )
619 self
. SetBrush ( Color
, "Solid" )
621 def SetPoints ( self
, Points
):
622 self
. Points
= array ( Points
, Float
)
623 self
. Points
. shape
= (- 1 , 2 ) # Make sure it is a NX2 array, even if there is only one point
624 self
. BoundingBox
= array ((( min ( self
. Points
[:, 0 ]),
625 min ( self
. Points
[:, 1 ]) ),
626 ( max ( self
. Points
[:, 0 ]),
627 max ( self
. Points
[:, 1 ]) ) ) )
629 self
._ Canvas
. BoundingBoxDirty
= True
631 def DrawD2 ( self
, dc
, Points
):
632 # A Little optimization for a diameter2 - point
633 dc
. DrawPointList ( Points
)
634 dc
. DrawPointList ( Points
+ ( 1 , 0 ))
635 dc
. DrawPointList ( Points
+ ( 0 , 1 ))
636 dc
. DrawPointList ( Points
+ ( 1 , 1 ))
638 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
640 Points
= WorldToPixel ( self
. Points
)
641 if self
. Diameter
<= 1 :
642 dc
. DrawPointList ( Points
)
643 elif self
. Diameter
<= 2 :
644 self
. DrawD2 ( dc
, Points
)
646 dc
. SetBrush ( self
. Brush
)
647 radius
= int ( round ( self
. Diameter
/ 2 ))
649 dc
. DrawEllipsePointSize ( ( xy
- radius
), ( self
. Diameter
, self
. Diameter
) )
650 if HTdc
and self
. HitAble
:
651 HTdc
. SetPen ( self
. HitPen
)
652 if self
. Diameter
<= 1 :
653 HTdc
. DrawPointList ( Points
)
654 elif self
. Diameter
<= 2 :
655 self
. DrawD2 ( HTdc
, Points
)
657 HTdc
. SetBrush ( self
. HitBrush
)
658 radius
= int ( round ( self
. Diameter
/ 2 ))
660 HTdc
. DrawEllipsePointSize ( ( xy
- radius
), ( self
. Diameter
, self
. Diameter
) )
662 #### Does anyone need this?
663 ##class Dot(DrawObject):
665 ## The Dot class takes an x.y coordinate pair, and the Diameter of the circle.
666 ## The Diameter is in pixels, so it won't change with zoom.
668 ## Also Fill and line data
671 ## def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,InForeground = False):
672 ## DrawObject.__init__(self,InForeground)
676 ## self.Diameter = Diameter
677 ## # NOTE: the bounding box does not include the diameter of the dot, as that is in pixel coords.
678 ## # If this is a problem, perhaps you should use a circle, instead!
679 ## self.BoundingBox = array(((x,y),(x,y)),Float)
681 ## self.LineColor = LineColor
682 ## self.LineStyle = LineStyle
683 ## self.LineWidth = LineWidth
684 ## self.FillColor = FillColor
685 ## self.FillStyle = FillStyle
687 ## self.SetPen(LineColor,LineStyle,LineWidth)
688 ## self.SetBrush(FillColor,FillStyle)
690 ## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
691 ## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
692 ## dc.SetPen(self.Pen)
693 ## dc.SetBrush(self.Brush)
694 ## radius = int(round(self.Diameter/2))
695 ## (X,Y) = WorldToPixel((self.X,self.Y))
696 ## dc.DrawEllipse((X - radius), (Y - radius), self.Diameter, self.Diameter)
698 class RectEllipse ( DrawObject
, XYObjectMixin
):
699 def __init__ ( self
, x
, y
, width
, height
,
705 InForeground
= False ):
707 DrawObject
.__ init
__ ( self
, InForeground
)
709 self
. XY
= array ( ( x
, y
), Float
)
710 self
. WH
= array ( ( width
, height
), Float
)
711 self
. BoundingBox
= array ((( x
, y
), ( self
. XY
+ self
. WH
)), Float
)
712 self
. LineColor
= LineColor
713 self
. LineStyle
= LineStyle
714 self
. LineWidth
= LineWidth
715 self
. FillColor
= FillColor
716 self
. FillStyle
= FillStyle
718 self
. HitLineWidth
= max ( LineWidth
, self
. MinHitLineWidth
)
720 self
. SetPen ( LineColor
, LineStyle
, LineWidth
)
721 self
. SetBrush ( FillColor
, FillStyle
)
724 def SetUpDraw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
):
726 dc
. SetBrush ( self
. Brush
)
727 if HTdc
and self
. HitAble
:
728 HTdc
. SetPen ( self
. HitPen
)
729 HTdc
. SetBrush ( self
. HitBrush
)
730 return ( WorldToPixel ( self
. XY
),
731 ScaleWorldToPixel ( self
. WH
) )
733 def SetXY ( self
, x
, y
):
734 self
. XY
= array ( ( x
, y
), Float
)
735 self
. BoundingBox
= array (( self
. XY
, ( self
. XY
+ self
. WH
) ), Float
)
737 self
._ Canvas
. BoundingBoxDirty
= True
740 class Rectangle ( RectEllipse
):
741 # def __init__(*args, **kwargs):
742 # RectEllipse.__init__(*args, **kwargs)
745 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
746 ( XY
, WH
) = self
. SetUpDraw ( dc
,
750 dc
. DrawRectanglePointSize ( XY
, WH
)
751 if HTdc
and self
. HitAble
:
752 HTdc
. DrawRectanglePointSize ( XY
, WH
)
754 class Ellipse ( RectEllipse
):
755 # def __init__(*args, **kwargs):
756 # RectEllipse.__init__(*args, **kwargs)
758 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
759 ( XY
, WH
) = self
. SetUpDraw ( dc
,
763 dc
. DrawEllipsePointSize ( XY
, WH
)
764 if HTdc
and self
. HitAble
:
765 HTdc
. DrawEllipsePointSize ( XY
, WH
)
767 class Circle ( Ellipse
):
768 def __init__ ( self
, x
, y
, Diameter
, ** kwargs
):
769 RectEllipse
.__ init
__ ( self
,
776 class TextObjectMixin
:
779 A mix in class that holds attributes and methods that are needed by
784 ## I'm caching fonts, because on GTK, getting a new font can take a
785 ## while. However, it gets cleared after every full draw as hanging
786 ## on to a bunch of large fonts takes a massive amount of memory.
790 def SetFont ( self
, Size
, Family
, Style
, Weight
, Underline
, FaceName
):
791 self
. Font
= self
. FontList
. setdefault ( ( Size
,
805 ## store the function that shift the coords for drawing text. The
806 ## "c" parameter is the correction for world coordinates, rather
807 ## than pixel coords as the y axis is reversed
808 ShiftFunDict
= { 'tl' : lambda x
, y
, w
, h
, world
= 0 : ( x
, y
) ,
809 'tc' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
/ 2 , y
) ,
810 'tr' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
, y
) ,
811 'cl' : lambda x
, y
, w
, h
, world
= 0 : ( x
, y
- h
/ 2 + world
* h
) ,
812 'cc' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
/ 2 , y
- h
/ 2 + world
* h
) ,
813 'cr' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
, y
- h
/ 2 + world
* h
) ,
814 'bl' : lambda x
, y
, w
, h
, world
= 0 : ( x
, y
- h
+ 2 * world
* h
) ,
815 'bc' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
/ 2 , y
- h
+ 2 * world
* h
) ,
816 'br' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
, y
- h
+ 2 * world
* h
)}
818 class Text ( DrawObject
, TextObjectMixin
):
820 This class creates a text object, placed at the coordinates,
821 x,y. the "Position" argument is a two charactor string, indicating
822 where in relation to the coordinates the string should be oriented.
824 The first letter is: t, c, or b, for top, center and bottom The
825 second letter is: l, c, or r, for left, center and right The
826 position refers to the position relative to the text itself. It
827 defaults to "tl" (top left).
829 Size is the size of the font in pixels, or in points for printing
830 (if it ever gets implimented). Those will be the same, If you assume
834 Font family, a generic way of referring to fonts without
835 specifying actual facename. One of:
836 wx.DEFAULT: Chooses a default font.
837 wx.DECORATIVE: A decorative font.
838 wx.ROMAN: A formal, serif font.
839 wx.SCRIPT: A handwriting font.
840 wx.SWISS: A sans-serif font.
841 wx.MODERN: A fixed pitch font.
842 NOTE: these are only as good as the wxWindows defaults, which aren't so good.
844 One of wx.NORMAL, wx.SLANT and wx.ITALIC.
846 One of wx.NORMAL, wx.LIGHT and wx.BOLD.
848 The value can be True or False. At present this may have an an
849 effect on Windows only.
851 Alternatively, you can set the kw arg: Font, to a wx.Font, and the above will be ignored.
853 The size is fixed, and does not scale with the drawing.
855 The hit-test is done on the entire text extent
859 def __init__ ( self
, String
, x
, y
,
862 BackgroundColor
= None ,
868 InForeground
= False ,
871 DrawObject
.__ init
__ ( self
, InForeground
)
874 # Input size in in Pixels, compute points size from PPI info.
875 # fixme: for printing, we'll have to do something a little different
876 self
. Size
= int ( round ( 72.0 * Size
/ ScreenPPI
))
879 self
. BackgroundColor
= BackgroundColor
884 FaceName
= Font
. GetFaceName ()
885 Family
= Font
. GetFamily ()
886 Size
= Font
. GetPointSize ()
887 Style
= Font
. GetStyle ()
888 Underlined
= Font
. GetUnderlined ()
889 Weight
= Font
. GetWeight ()
890 self
. SetFont ( Size
, Family
, Style
, Weight
, Underline
, FaceName
)
892 self
. BoundingBox
= array ((( x
, y
),( x
, y
)), Float
)
896 # use a memDC -- ScreenDC doesn't work with 2.5.1 and GTK2
898 #bitmap = wx.EmptyBitmap(1, 1)
899 #dc.SelectObject(bitmap)
900 #dc.SetFont(self.Font)
901 #(self.TextWidth, self.TextHeight) = dc.GetTextExtent(self.String)
902 ( self
. TextWidth
, self
. TextHeight
) = ( None , None )
903 self
. ShiftFun
= self
. ShiftFunDict
[ Position
]
905 def SetXY ( self
, x
, y
):
907 self
. BoundingBox
= array (( self
. XY
, self
. XY
), Float
)
909 self
._ Canvas
. BoundingBoxDirty
= True
911 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
912 XY
= WorldToPixel ( self
. XY
)
913 dc
. SetFont ( self
. Font
)
914 dc
. SetTextForeground ( self
. Color
)
915 if self
. BackgroundColor
:
916 dc
. SetBackgroundMode ( wx
. SOLID
)
917 dc
. SetTextBackground ( self
. BackgroundColor
)
919 dc
. SetBackgroundMode ( wx
. TRANSPARENT
)
920 if self
. TextWidth
is None or self
. TextHeight
is None :
921 ( self
. TextWidth
, self
. TextHeight
) = dc
. GetTextExtent ( self
. String
)
922 XY
= self
. ShiftFun ( XY
[ 0 ], XY
[ 1 ], self
. TextWidth
, self
. TextHeight
)
923 dc
. DrawTextPoint ( self
. String
, XY
)
924 if HTdc
and self
. HitAble
:
925 HTdc
. SetPen ( self
. HitPen
)
926 HTdc
. SetBrush ( self
. HitBrush
)
927 HTdc
. DrawRectanglePointSize ( XY
, ( self
. TextWidth
, self
. TextHeight
) )
929 class ScaledText ( DrawObject
, TextObjectMixin
, XYObjectMixin
):
931 This class creates a text object that is scaled when zoomed. It is
932 placed at the coordinates, x,y. the "Position" argument is a two
933 charactor string, indicating where in relation to the coordinates
934 the string should be oriented.
936 The first letter is: t, c, or b, for top, center and bottom The
937 second letter is: l, c, or r, for left, center and right The
938 position refers to the position relative to the text itself. It
939 defaults to "tl" (top left).
941 Size is the size of the font in world coordinates.
944 Font family, a generic way of referring to fonts without
945 specifying actual facename. One of:
946 wx.DEFAULT: Chooses a default font.
947 wx.DECORATI: A decorative font.
948 wx.ROMAN: A formal, serif font.
949 wx.SCRIPT: A handwriting font.
950 wx.SWISS: A sans-serif font.
951 wx.MODERN: A fixed pitch font.
952 NOTE: these are only as good as the wxWindows defaults, which aren't so good.
954 One of wx.NORMAL, wx.SLANT and wx.ITALIC.
956 One of wx.NORMAL, wx.LIGHT and wx.BOLD.
958 The value can be True or False. At present this may have an an
959 effect on Windows only.
961 Alternatively, you can set the kw arg: Font, to a wx.Font, and the
962 above will be ignored. The size of the font you specify will be
963 ignored, but the rest of it's attributes will be preserved.
965 The size will scale as the drawing is zoomed.
969 As fonts are scaled, the do end up a little different, so you don't
970 get exactly the same picture as you scale up and doen, but it's
973 On wxGTK1 on my Linux system, at least, using a font of over about
974 3000 pts. brings the system to a halt. It's the Font Server using
975 huge amounts of memory. My work around is to max the font size to
976 3000 points, so it won't scale past there. GTK2 uses smarter font
977 drawing, so that may not be an issue in future versions, so feel
978 free to test. Another smarter way to do it would be to set a global
979 zoom limit at that point.
981 The hit-test is done on the entire text extent. This could be made
982 optional, but I havn't gotten around to it.
986 def __init__ ( self
, String
, x
, y
, Size
,
988 BackgroundColor
= None ,
995 InForeground
= False ):
997 DrawObject
.__ init
__ ( self
, InForeground
)
1000 self
. XY
= array ( ( x
, y
), Float
)
1003 self
. BackgroundColor
= BackgroundColor
1004 self
. Family
= Family
1006 self
. Weight
= Weight
1007 self
. Underline
= Underline
1011 self
. FaceName
= Font
. GetFaceName ()
1012 self
. Family
= Font
. GetFamily ()
1013 self
. Style
= Font
. GetStyle ()
1014 self
. Underlined
= Font
. GetUnderlined ()
1015 self
. Weight
= Font
. GetWeight ()
1017 # Experimental max font size value on wxGTK2: this works OK on
1018 # my system If it's any larger, there is a crash, with the
1019 # message: The application 'FloatCanvasDemo.py' lost its
1020 # connection to the display :0.0; most likely the X server was
1021 # shut down or you killed/destroyed the application.
1024 self
. ShiftFun
= self
. ShiftFunDict
[ Position
]
1027 ## this isn't exact, as fonts don't scale exactly.
1029 bitmap
= wx
. EmptyBitmap ( 1 , 1 )
1030 dc
. SelectObject ( bitmap
) #wxMac needs a Bitmap selected for GetTextExtent to work.
1031 DrawingSize
= 40 # pts This effectively determines the resolution that the BB is computed to.
1032 ScaleFactor
= float ( Size
) / DrawingSize
1033 dc
. SetFont ( self
. SetFont ( DrawingSize
, self
. Family
, self
. Style
, self
. Weight
, self
. Underline
, self
. FaceName
) )
1034 ( w
, h
) = dc
. GetTextExtent ( self
. String
)
1037 x
, y
= self
. ShiftFun ( x
, y
, w
, h
, world
= 1 )
1038 self
. BoundingBox
= array ((( x
, y
- h
),( x
+ w
, y
)), Float
)
1040 # the new coords are set to the corner of the BB:
1041 #self.X = self.BoundingBox[0,0]
1042 #self.Y = self.BoundingBox[1,1]
1043 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
1044 ( X
, Y
) = WorldToPixel ( ( self
. XY
) )
1046 # compute the font size:
1047 Size
= abs ( ScaleWorldToPixel ( ( self
. Size
, self
. Size
) )[ 1 ] ) # only need a y coordinate length
1048 ## Check to see if the font size is large enough to blow up the X font server
1049 ## If so, limit it. Would it be better just to not draw it?
1050 ## note that this limit is dependent on how much memory you have, etc.
1051 if Size
> self
. MaxSize
:
1053 dc
. SetFont ( self
. SetFont ( Size
, self
. Family
, self
. Style
, self
. Weight
, self
. Underline
, self
. FaceName
))
1054 dc
. SetTextForeground ( self
. Color
)
1055 if self
. BackgroundColor
:
1056 dc
. SetBackgroundMode ( wx
. SOLID
)
1057 dc
. SetTextBackground ( self
. BackgroundColor
)
1059 dc
. SetBackgroundMode ( wx
. TRANSPARENT
)
1060 ( w
, h
) = dc
. GetTextExtent ( self
. String
)
1061 # compute the shift, and adjust the coordinates, if neccesary
1062 # This had to be put in here, because it changes with Zoom, as
1063 # fonts don't scale exactly.
1064 xy
= self
. ShiftFun ( X
, Y
, w
, h
)
1066 dc
. DrawTextPoint ( self
. String
, xy
)
1067 if HTdc
and self
. HitAble
:
1068 HTdc
. SetPen ( self
. HitPen
)
1069 HTdc
. SetBrush ( self
. HitBrush
)
1070 HTdc
. DrawRectanglePointSize ( xy
, ( w
, h
) )
1073 #---------------------------------------------------------------------------
1074 class FloatCanvas ( wx
. Panel
):
1078 This is a high level window for drawing maps and anything else in an
1079 arbitrary coordinate system.
1081 The goal is to provide a convenient way to draw stuff on the screen
1082 without having to deal with handling OnPaint events, converting to pixel
1083 coordinates, knowing about wxWindows brushes, pens, and colors, etc. It
1084 also provides virtually unlimited zooming and scrolling
1086 I am using it for two things:
1087 1) general purpose drawing in floating point coordinates
1088 2) displaying map data in Lat-long coordinates
1090 If the projection is set to None, it will draw in general purpose
1091 floating point coordinates. If the projection is set to 'FlatEarth', it
1092 will draw a FlatEarth projection, centered on the part of the map that
1093 you are viewing. You can also pass in your own projection function.
1095 It is double buffered, so re-draws after the window is uncovered by something
1096 else are very quick.
1098 It relies on NumPy, which is needed for speed (maybe, I havn't profiled it)
1100 Bugs and Limitations:
1101 Lots: patches, fixes welcome
1103 For Map drawing: It ignores the fact that the world is, in fact, a
1104 sphere, so it will do strange things if you are looking at stuff near
1105 the poles or the date line. so far I don't have a need to do that, so I
1106 havn't bothered to add any checks for that yet.
1109 I have set no zoom limits. What this means is that if you zoom in really
1110 far, you can get integer overflows, and get wierd results. It
1111 doesn't seem to actually cause any problems other than wierd output, at
1112 least when I have run it.
1115 I have done a couple of things to improve speed in this app. The one
1116 thing I have done is used NumPy Arrays to store the coordinates of the
1117 points of the objects. This allowed me to use array oriented functions
1118 when doing transformations, and should provide some speed improvement
1119 for objects with a lot of points (big polygons, polylines, pointsets).
1121 The real slowdown comes when you have to draw a lot of objects, because
1122 you have to call the wx.DC.DrawSomething call each time. This is plenty
1123 fast for tens of objects, OK for hundreds of objects, but pretty darn
1124 slow for thousands of objects.
1126 The solution is to be able to pass some sort of object set to the DC
1127 directly. I've used DC.DrawPointList(Points), and it helped a lot with
1128 drawing lots of points. I havn't got a LineSet type object, so I havn't
1129 used DC.DrawLineList yet. I'd like to get a full set of DrawStuffList()
1130 methods implimented, and then I'd also have a full set of Object sets
1131 that could take advantage of them. I hope to get to it some day.
1135 At this point, there are a full set of custom mouse events. They are
1136 just like the rebulsr mouse events, but include an extra attribute:
1137 Event.GetCoords(), that returns the (x,y) position in world
1138 coordinates, as a length-2 NumPy vector of Floats.
1140 Copyright: Christopher Barker
1142 License: Same as the version of wxPython you are using it with
1144 Please let me know if you're using this!!!
1148 Chris.Barker@noaa.gov
1152 def __init__ ( self
, parent
, id = - 1 ,
1153 size
= wx
. DefaultSize
,
1154 ProjectionFun
= None ,
1155 BackgroundColor
= "WHITE" ,
1158 wx
. Panel
.__ init
__ ( self
, parent
, id , wx
. DefaultPosition
, size
)
1160 global ScreenPPI
## A global variable to hold the Pixels per inch that wxWindows thinks is in use.
1162 ScreenPPI
= dc
. GetPPI ()[ 0 ] # Assume square pixels
1165 self
. HitColorGenerator
= None
1166 self
. UseHitTest
= None
1168 self
. NumBetweenBlits
= 500
1170 self
. BackgroundBrush
= wx
. Brush ( BackgroundColor
, wx
. SOLID
)
1174 wx
. EVT_PAINT ( self
, self
. OnPaint
)
1175 wx
. EVT_SIZE ( self
, self
. OnSize
)
1177 wx
. EVT_LEFT_DOWN ( self
, self
. LeftDownEvent
)
1178 wx
. EVT_LEFT_UP ( self
, self
. LeftUpEvent
)
1179 wx
. EVT_LEFT_DCLICK ( self
, self
. LeftDoubleClickEvent
)
1180 wx
. EVT_MIDDLE_DOWN ( self
, self
. MiddleDownEvent
)
1181 wx
. EVT_MIDDLE_UP ( self
, self
. MiddleUpEvent
)
1182 wx
. EVT_MIDDLE_DCLICK ( self
, self
. MiddleDoubleClickEvent
)
1183 wx
. EVT_RIGHT_DOWN ( self
, self
. RightDownEvent
)
1184 wx
. EVT_RIGHT_UP ( self
, self
. RightUpEvent
)
1185 wx
. EVT_RIGHT_DCLICK ( self
, self
. RightDoubleCLickEvent
)
1186 wx
. EVT_MOTION ( self
, self
. MotionEvent
)
1187 wx
. EVT_MOUSEWHEEL ( self
, self
. WheelEvent
)
1189 ## CHB: I'm leaving these out for now.
1190 #wx.EVT_ENTER_WINDOW(self, self. )
1191 #wx.EVT_LEAVE_WINDOW(self, self. )
1193 ## create the Hit Test Dicts:
1198 self
._ ForeDrawList
= []
1199 self
._ ForegroundBuffer
= None
1200 self
. BoundingBox
= None
1201 self
. BoundingBoxDirty
= False
1202 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
1204 self
. SetProjectionFun ( ProjectionFun
)
1206 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
) # No Projection to start!
1207 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
) # default Transformation
1212 self
. StartRBBox
= None
1213 self
. PrevRBBox
= None
1214 self
. StartMove
= None
1215 self
. PrevMoveXY
= None
1216 self
. ObjectUnderMouse
= None
1218 # called just to make sure everything is initialized
1223 def SetProjectionFun ( self
, ProjectionFun
):
1224 if ProjectionFun
== 'FlatEarth' :
1225 self
. ProjectionFun
= self
. FlatEarthProjection
1226 elif type ( ProjectionFun
) == types
. FunctionType
:
1227 self
. ProjectionFun
= ProjectionFun
1228 elif ProjectionFun
is None :
1229 self
. ProjectionFun
= lambda x
= None : array ( ( 1 , 1 ), Float
)
1231 raise FloatCanvasException ( 'Projectionfun must be either: "FlatEarth", None, or a function that takes the ViewPortCenter and returns a MapProjectionVector' )
1233 def FlatEarthProjection ( self
, CenterPoint
):
1234 return array (( cos ( pi
* CenterPoint
[ 1 ]/ 180 ), 1 ), Float
)
1236 def SetMode ( self
, Mode
):
1237 if Mode
in [ "ZoomIn" , "ZoomOut" , "Move" , "Mouse" , None ]:
1240 raise FloatCanvasException ( '" %s " is Not a valid Mode' % Mode
)
1242 def MakeHitDict ( self
):
1243 ##fixme: Should this just be None if nothing has been bound?
1244 self
. HitDict
= {EVT_FC_LEFT_DOWN: {}
,
1246 EVT_FC_LEFT_DCLICK
: {},
1247 EVT_FC_MIDDLE_DOWN
: {},
1248 EVT_FC_MIDDLE_UP
: {},
1249 EVT_FC_MIDDLE_DCLICK
: {},
1250 EVT_FC_RIGHT_DOWN
: {},
1251 EVT_FC_RIGHT_UP
: {},
1252 EVT_FC_RIGHT_DCLICK
: {},
1253 EVT_FC_ENTER_OBJECT
: {},
1254 EVT_FC_LEAVE_OBJECT
: {},
1257 def RaiseMouseEvent ( self
, Event
, EventType
):
1259 This is called in various other places to raise a Mouse Event
1261 #print "in Raise Mouse Event", Event
1262 pt
= self
. PixelToWorld ( Event
. GetPosition () )
1263 evt
= MouseEvent ( EventType
, Event
, self
. GetId (), pt
)
1264 self
. GetEventHandler (). ProcessEvent ( evt
)
1266 def HitTest ( self
, event
, HitEvent
):
1268 # check if there are any objects in the dict for this event
1269 if self
. HitDict
[ HitEvent
]:
1270 xy
= event
. GetPosition ()
1271 if self
._ ForegroundHTdc
:
1272 hitcolor
= self
._ ForegroundHTdc
. GetPixelPoint ( xy
)
1274 hitcolor
= self
._ HTdc
. GetPixelPoint ( xy
)
1275 color
= ( hitcolor
. Red (), hitcolor
. Green (), hitcolor
. Blue () )
1276 if color
in self
. HitDict
[ HitEvent
]:
1277 Object
= self
. HitDict
[ HitEvent
][ color
]
1278 ## Add the hit coords to the Object
1279 Object
. HitCoords
= self
. PixelToWorld ( xy
)
1280 Object
. CallBackFuncs
[ HitEvent
]( Object
)
1284 def MouseOverTest ( self
, event
):
1285 ##fixme: Can this be cleaned up?
1287 xy
= event
. GetPosition ()
1288 if self
._ ForegroundHTdc
:
1289 hitcolor
= self
._ ForegroundHTdc
. GetPixelPoint ( xy
)
1291 hitcolor
= self
._ HTdc
. GetPixelPoint ( xy
)
1292 color
= ( hitcolor
. Red (), hitcolor
. Green (), hitcolor
. Blue () )
1293 OldObject
= self
. ObjectUnderMouse
1294 ObjectCallbackCalled
= False
1295 if color
in self
. HitDict
[ EVT_FC_ENTER_OBJECT
]:
1296 Object
= self
. HitDict
[ EVT_FC_ENTER_OBJECT
][ color
]
1297 if ( OldObject
is None ):
1299 Object
. CallBackFuncs
[ EVT_FC_ENTER_OBJECT
]( Object
)
1300 ObjectCallbackCalled
= True
1302 pass # this means the enter event isn't bound for that object
1303 elif OldObject
== Object
: # the mouse is still on the same object
1305 ## Is the mouse on a differnt object as it was...
1306 elif not ( Object
== OldObject
):
1307 # call the leave object callback
1309 OldObject
. CallBackFuncs
[ EVT_FC_LEAVE_OBJECT
]( OldObject
)
1310 ObjectCallbackCalled
= True
1312 pass # this means the leave event isn't bound for that object
1314 Object
. CallBackFuncs
[ EVT_FC_ENTER_OBJECT
]( Object
)
1315 ObjectCallbackCalled
= True
1317 pass # this means the enter event isn't bound for that object
1318 ## set the new object under mouse
1319 self
. ObjectUnderMouse
= Object
1320 elif color
in self
. HitDict
[ EVT_FC_LEAVE_OBJECT
]:
1321 Object
= self
. HitDict
[ EVT_FC_LEAVE_OBJECT
][ color
]
1322 self
. ObjectUnderMouse
= Object
1324 # no objects under mouse bound to mouse-over events
1325 self
. ObjectUnderMouse
= None
1328 OldObject
. CallBackFuncs
[ EVT_FC_LEAVE_OBJECT
]( OldObject
)
1329 ObjectCallbackCalled
= True
1331 pass # this means the leave event isn't bound for that object
1332 return ObjectCallbackCalled
1335 ## fixme: There is a lot of repeated code here
1336 ## Is there a better way?
1337 def LeftDoubleClickEvent ( self
, event
):
1338 if self
. GUIMode
== "Mouse" :
1339 EventType
= EVT_FC_LEFT_DCLICK
1340 if not self
. HitTest ( event
, EventType
):
1341 self
. RaiseMouseEvent ( event
, EventType
)
1344 def MiddleDownEvent ( self
, event
):
1345 if self
. GUIMode
== "Mouse" :
1346 EventType
= EVT_FC_MIDDLE_DOWN
1347 if not self
. HitTest ( event
, EventType
):
1348 self
. RaiseMouseEvent ( event
, EventType
)
1350 def MiddleUpEvent ( self
, event
):
1351 if self
. GUIMode
== "Mouse" :
1352 EventType
= EVT_FC_MIDDLE_UP
1353 if not self
. HitTest ( event
, EventType
):
1354 self
. RaiseMouseEvent ( event
, EventType
)
1356 def MiddleDoubleClickEvent ( self
, event
):
1357 if self
. GUIMode
== "Mouse" :
1358 EventType
= EVT_FC_MIDDLE_DCLICK
1359 if not self
. HitTest ( event
, EventType
):
1360 self
. RaiseMouseEvent ( event
, EventType
)
1362 def RightUpEvent ( self
, event
):
1363 if self
. GUIMode
== "Mouse" :
1364 EventType
= EVT_FC_RIGHT_UP
1365 if not self
. HitTest ( event
, EventType
):
1366 self
. RaiseMouseEvent ( event
, EventType
)
1368 def RightDoubleCLickEvent ( self
, event
):
1369 if self
. GUIMode
== "Mouse" :
1370 EventType
= EVT_FC_RIGHT_DCLICK
1371 if not self
. HitTest ( event
, EventType
):
1372 self
. RaiseMouseEvent ( event
, EventType
)
1374 def WheelEvent ( self
, event
):
1375 if self
. GUIMode
== "Mouse" :
1376 self
. RaiseMouseEvent ( event
, EVT_FC_MOUSEWHEEL
)
1379 def LeftDownEvent ( self
, event
):
1381 if self
. GUIMode
== "ZoomIn" :
1382 self
. StartRBBox
= array ( event
. GetPosition () )
1383 self
. PrevRBBox
= None
1385 elif self
. GUIMode
== "ZoomOut" :
1386 Center
= self
. PixelToWorld ( event
. GetPosition () )
1387 self
. Zoom ( 1 / 1.5 , Center
)
1388 elif self
. GUIMode
== "Move" :
1389 self
. StartMove
= array ( event
. GetPosition () )
1390 self
. PrevMoveXY
= ( 0 , 0 )
1391 elif self
. GUIMode
== "Mouse" :
1393 if not self
. HitTest ( event
, EVT_FC_LEFT_DOWN
):
1394 self
. RaiseMouseEvent ( event
, EVT_FC_LEFT_DOWN
)
1398 def LeftUpEvent ( self
, event
):
1399 if self
. HasCapture ():
1402 if self
. GUIMode
== "ZoomIn" :
1403 if event
. LeftUp () and not self
. StartRBBox
is None :
1404 self
. PrevRBBox
= None
1405 EndRBBox
= event
. GetPosition ()
1406 StartRBBox
= self
. StartRBBox
1407 # if mouse has moved less that ten pixels, don't use the box.
1408 if ( abs ( StartRBBox
[ 0 ] - EndRBBox
[ 0 ]) > 10
1409 and abs ( StartRBBox
[ 1 ] - EndRBBox
[ 1 ]) > 10 ):
1410 EndRBBox
= self
. PixelToWorld ( EndRBBox
)
1411 StartRBBox
= self
. PixelToWorld ( StartRBBox
)
1412 BB
= array ((( min ( EndRBBox
[ 0 ], StartRBBox
[ 0 ]),
1413 min ( EndRBBox
[ 1 ], StartRBBox
[ 1 ])),
1414 ( max ( EndRBBox
[ 0 ], StartRBBox
[ 0 ]),
1415 max ( EndRBBox
[ 1 ], StartRBBox
[ 1 ]))), Float
)
1418 Center
= self
. PixelToWorld ( StartRBBox
)
1419 self
. Zoom ( 1.5 , Center
)
1420 self
. StartRBBox
= None
1421 elif self
. GUIMode
== "Move" :
1422 if not self
. StartMove
is None :
1423 StartMove
= self
. StartMove
1424 EndMove
= array (( event
. GetX (), event
. GetY ()))
1425 if sum (( StartMove
- EndMove
)** 2 ) > 16 :
1426 self
. Move ( StartMove
- EndMove
, 'Pixel' )
1427 self
. StartMove
= None
1428 elif self
. GUIMode
== "Mouse" :
1429 EventType
= EVT_FC_LEFT_UP
1430 if not self
. HitTest ( event
, EventType
):
1431 self
. RaiseMouseEvent ( event
, EventType
)
1435 def MotionEvent ( self
, event
):
1437 if self
. GUIMode
== "ZoomIn" :
1438 if event
. Dragging () and event
. LeftIsDown () and not ( self
. StartRBBox
is None ):
1439 xy0
= self
. StartRBBox
1440 xy1
= array ( event
. GetPosition () )
1442 wh
[ 0 ] = max ( wh
[ 0 ], int ( wh
[ 1 ]* self
. AspectRatio
))
1443 wh
[ 1 ] = int ( wh
[ 0 ] / self
. AspectRatio
)
1444 xy_c
= ( xy0
+ xy1
) / 2
1445 dc
= wx
. ClientDC ( self
)
1447 dc
. SetPen ( wx
. Pen ( 'WHITE' , 2 , wx
. SHORT_DASH
))
1448 dc
. SetBrush ( wx
. TRANSPARENT_BRUSH
)
1449 dc
. SetLogicalFunction ( wx
. XOR
)
1451 dc
. DrawRectanglePointSize (* self
. PrevRBBox
)
1452 self
. PrevRBBox
= ( xy_c
- wh
/ 2 , wh
)
1453 dc
. DrawRectanglePointSize ( * self
. PrevRBBox
)
1455 elif self
. GUIMode
== "Move" :
1456 if event
. Dragging () and event
. LeftIsDown () and not self
. StartMove
is None :
1457 xy1
= array ( event
. GetPosition () )
1459 xy_tl
= xy1
- self
. StartMove
1460 dc
= wx
. ClientDC ( self
)
1462 x1
, y1
= self
. PrevMoveXY
1464 w
, h
= self
. PanelSize
1465 if x2
> x1
and y2
> y1
:
1472 elif x2
> x1
and y2
<= y1
:
1481 elif x2
<= x1
and y2
> y1
:
1490 elif x2
<= x1
and y2
<= y1
:
1500 dc
. SetPen ( wx
. TRANSPARENT_PEN
)
1501 dc
. SetBrush ( self
. BackgroundBrush
)
1502 dc
. DrawRectangle ( xa
, ya
, wa
, ha
)
1503 dc
. DrawRectangle ( xb
, yb
, wb
, hb
)
1504 self
. PrevMoveXY
= xy_tl
1505 if self
._ ForegroundBuffer
:
1506 dc
. DrawBitmapPoint ( self
._ ForegroundBuffer
, xy_tl
)
1508 dc
. DrawBitmapPoint ( self
._ Buffer
, xy_tl
)
1510 elif self
. GUIMode
== "Mouse" :
1511 ## Only do something if there are mouse over events bound
1512 if self
. HitDict
and ( self
. HitDict
[ EVT_FC_ENTER_OBJECT
] or self
. HitDict
[ EVT_FC_LEAVE_OBJECT
] ):
1513 if not self
. MouseOverTest ( event
):
1514 self
. RaiseMouseEvent ( event
, EVT_FC_MOTION
)
1517 self
. RaiseMouseEvent ( event
, EVT_FC_MOTION
)
1521 def RightDownEvent ( self
, event
):
1523 if self
. GUIMode
== "ZoomIn" :
1524 Center
= self
. PixelToWorld (( event
. GetX (), event
. GetY ()))
1525 self
. Zoom ( 1 / 1.5 , Center
)
1526 elif self
. GUIMode
== "ZoomOut" :
1527 Center
= self
. PixelToWorld (( event
. GetX (), event
. GetY ()))
1528 self
. Zoom ( 1.5 , Center
)
1529 elif self
. GUIMode
== "Mouse" :
1530 EventType
= EVT_FC_RIGHT_DOWN
1531 if not self
. HitTest ( event
, EventType
):
1532 self
. RaiseMouseEvent ( event
, EventType
)
1536 def MakeNewBuffers ( self
):
1537 self
._ BackgroundDirty
= True
1538 # Make new offscreen bitmap:
1539 self
._ Buffer
= wx
. EmptyBitmap (* self
. PanelSize
)
1541 #dc.SelectObject(self._Buffer)
1543 if self
._ ForeDrawList
:
1544 self
._ ForegroundBuffer
= wx
. EmptyBitmap (* self
. PanelSize
)
1546 self
._ ForegroundBuffer
= None
1551 self
._ ForegroundHTdc
= None
1553 def MakeNewHTdc ( self
):
1554 ## Note: While it's considered a "bad idea" to keep a
1555 ## MemoryDC around I'm doing it here because a wx.Bitmap
1556 ## doesn't have a GetPixel method so a DC is needed to do
1557 ## the hit-test. It didn't seem like a good idea to re-create
1558 ## a wx.MemoryDC on every single mouse event, so I keep it
1560 self
._ HTdc
= wx
. MemoryDC ()
1561 self
._ HTBitmap
= wx
. EmptyBitmap (* self
. PanelSize
)
1562 self
._ HTdc
. SelectObject ( self
._ HTBitmap
)
1563 self
._ HTdc
. SetBackground ( wx
. BLACK_BRUSH
)
1564 if self
._ ForeDrawList
:
1565 self
._ ForegroundHTdc
= wx
. MemoryDC ()
1566 self
._ ForegroundHTBitmap
= wx
. EmptyBitmap (* self
. PanelSize
)
1567 self
._ ForegroundHTdc
. SelectObject ( self
._ ForegroundHTBitmap
)
1568 self
._ ForegroundHTdc
. SetBackground ( wx
. BLACK_BRUSH
)
1570 self
._ ForegroundHTdc
= None
1572 def OnSize ( self
, event
):
1573 self
. PanelSize
= array ( self
. GetClientSizeTuple (), Int32
)
1574 self
. HalfPanelSize
= self
. PanelSize
/ 2 # lrk: added for speed in WorldToPixel
1575 if self
. PanelSize
[ 0 ] == 0 or self
. PanelSize
[ 1 ] == 0 :
1576 self
. AspectRatio
= 1.0
1578 self
. AspectRatio
= float ( self
. PanelSize
[ 0 ]) / self
. PanelSize
[ 1 ]
1579 self
. MakeNewBuffers ()
1582 def OnPaint ( self
, event
):
1583 dc
= wx
. PaintDC ( self
)
1584 if self
._ ForegroundBuffer
:
1585 dc
. DrawBitmap ( self
._ ForegroundBuffer
, 0 , 0 )
1587 dc
. DrawBitmap ( self
._ Buffer
, 0 , 0 )
1589 def Draw ( self
, Force
= False ):
1591 There is a main buffer set up to double buffer the screen, so
1592 you can get quick re-draws when the window gets uncovered.
1594 If there are any objects in self._ForeDrawList, then the
1595 background gets drawn to a new buffer, and the foreground
1596 objects get drawn on top of it. The final result if blitted to
1597 the screen, and stored for future Paint events. This is done so
1598 that you can have a complicated background, but have something
1599 changing on the foreground, without having to wait for the
1600 background to get re-drawn. This can be used to support simple
1601 animation, for instance.
1605 if self
. PanelSize
< ( 1 , 1 ):
1607 if self
. Debug
: start
= clock ()
1608 ScreenDC
= wx
. ClientDC ( self
)
1609 ViewPortWorld
= ( self
. PixelToWorld (( 0 , 0 )),
1610 self
. PixelToWorld ( self
. PanelSize
) )
1611 ViewPortBB
= array ( ( minimum
. reduce ( ViewPortWorld
),
1612 maximum
. reduce ( ViewPortWorld
) ) )
1614 dc
. SelectObject ( self
._ Buffer
)
1615 if self
._ BackgroundDirty
or Force
:
1616 #print "Background is Dirty"
1617 dc
. SetBackground ( self
. BackgroundBrush
)
1621 self
._ DrawObjects
( dc
, self
._ DrawList
, ScreenDC
, ViewPortBB
, self
._ HTdc
)
1622 self
._ BackgroundDirty
= False
1624 if self
._ ForeDrawList
:
1625 ## If an object was just added to the Foreground, there might not yet be a buffer
1626 if self
._ ForegroundBuffer
is None :
1627 self
._ ForegroundBuffer
= wx
. EmptyBitmap ( self
. PanelSize
[ 0 ],
1630 dc
= wx
. MemoryDC () ## I got some strange errors (linewidths wrong) if I didn't make a new DC here
1631 dc
. SelectObject ( self
._ ForegroundBuffer
)
1632 dc
. DrawBitmap ( self
._ Buffer
, 0 , 0 )
1633 if self
._ ForegroundHTdc
is None :
1634 self
._ ForegroundHTdc
= wx
. MemoryDC ()
1635 self
._ ForegroundHTdc
. SelectObject ( wx
. EmptyBitmap (
1637 self
. PanelSize
[ 1 ]) )
1639 ## blit the background HT buffer to the foreground HT buffer
1640 self
._ ForegroundHTdc
. Blit ( 0 , 0 ,
1641 self
. PanelSize
[ 0 ], self
. PanelSize
[ 1 ],
1643 self
._ DrawObjects
( dc
,
1647 self
._ ForegroundHTdc
)
1648 ScreenDC
. Blit ( 0 , 0 , self
. PanelSize
[ 0 ], self
. PanelSize
[ 1 ], dc
, 0 , 0 )
1649 # If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn
1650 # This seeems out of place, but it works.
1652 ScreenDC
. SetPen ( wx
. Pen ( 'WHITE' , 2 , wx
. SHORT_DASH
))
1653 ScreenDC
. SetBrush ( wx
. TRANSPARENT_BRUSH
)
1654 ScreenDC
. SetLogicalFunction ( wx
. XOR
)
1655 ScreenDC
. DrawRectanglePointSize (* self
. PrevRBBox
)
1656 if self
. Debug
: print "Drawing took %f seconds of CPU time" %( clock ()- start
)
1658 ## Clear the font cache
1659 ## IF you don't do this, the X font server starts to take up Massive amounts of memory
1660 ## This is mostly a problem with very large fonts, that you get with scaled text when zoomed in.
1661 DrawObject
. FontList
= {}
1663 def _ShouldRedraw ( DrawList
, ViewPortBB
): # lrk: adapted code from BBCheck
1664 # lrk: Returns the objects that should be redrawn
1668 for Object
in DrawList
:
1669 BB1
= Object
. BoundingBox
1670 if ( BB1
[ 1 , 0 ] > BB2
[ 0 , 0 ] and BB1
[ 0 , 0 ] < BB2
[ 1 , 0 ] and
1671 BB1
[ 1 , 1 ] > BB2
[ 0 , 1 ] and BB1
[ 0 , 1 ] < BB2
[ 1 , 1 ]):
1672 redrawlist
. append ( Object
)
1674 _ShouldRedraw
= staticmethod ( _ShouldRedraw
)
1677 ## def BBCheck(self, BB1, BB2):
1680 ## BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise
1683 ## if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and
1684 ## (BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ):
1689 def Move ( self
, shift
, CoordType
):
1691 move the image in the window.
1693 shift is an (x,y) tuple, specifying the amount to shift in each direction
1695 It can be in any of three coordinates: Panel, Pixel, World,
1696 specified by the CoordType parameter
1698 Panel coordinates means you want to shift the image by some
1699 fraction of the size of the displaed image
1701 Pixel coordinates means you want to shift the image by some number of pixels
1703 World coordinates mean you want to shift the image by an amount
1704 in Floating point world coordinates
1708 shift
= array ( shift
, Float
)
1709 if CoordType
== 'Panel' : # convert from panel coordinates
1710 shift
= shift
* array ((- 1 , 1 ), Float
) * self
. PanelSize
/ self
. TransformVector
1711 elif CoordType
== 'Pixel' : # convert from pixel coordinates
1712 shift
= shift
/ self
. TransformVector
1713 elif CoordType
== 'World' : # No conversion
1716 raise FloatCanvasException ( 'CoordType must be either "Panel", "Pixel", or "World"' )
1718 self
. ViewPortCenter
= self
. ViewPortCenter
+ shift
1719 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1720 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
) * self
. MapProjectionVector
1721 self
._ BackgroundDirty
= True
1724 def Zoom ( self
, factor
, center
= None ):
1727 Zoom(factor, center) changes the amount of zoom of the image by factor.
1728 If factor is greater than one, the image gets larger.
1729 If factor is less than one, the image gets smaller.
1731 Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming.
1732 If center is not given, the center will stay the same.
1735 self
. Scale
= self
. Scale
* factor
1736 if not center
is None :
1737 self
. ViewPortCenter
= array ( center
, Float
)
1738 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1739 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
) * self
. MapProjectionVector
1740 self
._ BackgroundDirty
= True
1743 def ZoomToBB ( self
, NewBB
= None , DrawFlag
= True ):
1747 Zooms the image to the bounding box given, or to the bounding
1748 box of all the objects on the canvas, if none is given.
1752 if not NewBB
is None :
1755 if self
. BoundingBoxDirty
:
1756 self
._ ResetBoundingBox
()
1757 BoundingBox
= self
. BoundingBox
1758 if not BoundingBox
is None :
1759 self
. ViewPortCenter
= array ((( BoundingBox
[ 0 , 0 ]+ BoundingBox
[ 1 , 0 ])/ 2 ,
1760 ( BoundingBox
[ 0 , 1 ]+ BoundingBox
[ 1 , 1 ])/ 2 ), Float
)
1761 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1762 # Compute the new Scale
1763 BoundingBox
= BoundingBox
* self
. MapProjectionVector
1765 self
. Scale
= min ( abs ( self
. PanelSize
[ 0 ] / ( BoundingBox
[ 1 , 0 ]- BoundingBox
[ 0 , 0 ])),
1766 abs ( self
. PanelSize
[ 1 ] / ( BoundingBox
[ 1 , 1 ]- BoundingBox
[ 0 , 1 ])) )* 0.95
1767 except ZeroDivisionError : # this will happen if the BB has zero width or height
1769 self
. Scale
= ( self
. PanelSize
[ 0 ] / ( BoundingBox
[ 1 , 0 ]- BoundingBox
[ 0 , 0 ]))* 0.95
1770 except ZeroDivisionError :
1772 self
. Scale
= ( self
. PanelSize
[ 1 ] / ( BoundingBox
[ 1 , 1 ]- BoundingBox
[ 0 , 1 ]))* 0.95
1773 except ZeroDivisionError : #zero size! (must be a single point)
1776 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
)* self
. MapProjectionVector
1778 self
._ BackgroundDirty
= True
1781 # Reset the shifting and scaling to defaults when there is no BB
1782 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
1783 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
) # No Projection to start!
1784 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
) # default Transformation
1787 def RemoveObjects ( self
, Objects
):
1788 for Object
in Objects
:
1789 self
. RemoveObject ( Object
, ResetBB
= False )
1790 self
. BoundingBoxDirty
= True
1792 def RemoveObject ( self
, Object
, ResetBB
= True ):
1793 ##fixme: Using the list.remove method is kind of slow
1794 if Object
. InForeground
:
1795 self
._ ForeDrawList
. remove ( Object
)
1797 self
._ DrawList
. remove ( Object
)
1798 self
._ BackgroundDirty
= True
1800 self
. BoundingBoxDirty
= True
1802 def ClearAll ( self
, ResetBB
= True ):
1804 self
._ ForeDrawList
= []
1805 self
._ BackgroundDirty
= True
1806 self
. HitColorGenerator
= None
1807 self
. UseHitTest
= False
1809 self
._ ResetBoundingBox
()
1810 self
. MakeNewBuffers ()
1814 ## def _AddBoundingBox(self,NewBB):
1815 ## if self.BoundingBox is None:
1816 ## self.BoundingBox = NewBB
1817 ## self.ZoomToBB(NewBB,DrawFlag = False)
1819 ## self.BoundingBox = array( ( (min(self.BoundingBox[0,0],NewBB[0,0]),
1820 ## min(self.BoundingBox[0,1],NewBB[0,1])),
1821 ## (max(self.BoundingBox[1,0],NewBB[1,0]),
1822 ## max(self.BoundingBox[1,1],NewBB[1,1]))),
1825 def _getboundingbox ( bboxarray
): # lrk: added this
1827 upperleft
= minimum
. reduce ( bboxarray
[:, 0 ])
1828 lowerright
= maximum
. reduce ( bboxarray
[:, 1 ])
1829 return array (( upperleft
, lowerright
), Float
)
1831 _getboundingbox
= staticmethod ( _getboundingbox
)
1833 def _ResetBoundingBox ( self
):
1834 if self
._ DrawList
or self
._ ForeDrawList
:
1835 bboxarray
= zeros (( len ( self
._ DrawList
)+ len ( self
._ ForeDrawList
), 2 , 2 ), Float
)
1836 i
= - 1 # just in case _DrawList is empty
1837 for ( i
, BB
) in enumerate ( self
._ DrawList
):
1838 bboxarray
[ i
] = BB
. BoundingBox
1839 for ( j
, BB
) in enumerate ( self
._ ForeDrawList
):
1840 bboxarray
[ i
+ j
+ 1 ] = BB
. BoundingBox
1841 self
. BoundingBox
= self
._ getboundingbox
( bboxarray
)
1843 self
. BoundingBox
= None
1844 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
1845 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
)
1846 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
)
1848 self
. BoundingBoxDirty
= False
1850 def PixelToWorld ( self
, Points
):
1852 Converts coordinates from Pixel coordinates to world coordinates.
1854 Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates.
1857 return ((( asarray ( Points
, Float
) - ( self
. PanelSize
/ 2 ))/ self
. TransformVector
) + self
. ViewPortCenter
)
1859 def WorldToPixel ( self
, Coordinates
):
1861 This function will get passed to the drawing functions of the objects,
1862 to transform from world to pixel coordinates.
1863 Coordinates should be a NX2 array of (x,y) coordinates, or
1864 a 2-tuple, or sequence of 2-tuples.
1866 #Note: this can be called by users code for various reasons, so asarray is needed.
1867 return ((( asarray ( Coordinates
, Float
) - self
. ViewPortCenter
)* self
. TransformVector
)+( self
. HalfPanelSize
)). astype ( 'i' )
1869 def ScaleWorldToPixel ( self
, Lengths
):
1871 This function will get passed to the drawing functions of the objects,
1872 to Change a length from world to pixel coordinates.
1874 Lengths should be a NX2 array of (x,y) coordinates, or
1875 a 2-tuple, or sequence of 2-tuples.
1877 return ( ( asarray ( Lengths
, Float
)* self
. TransformVector
) ). astype ( 'i' )
1879 def ScalePixelToWorld ( self
, Lengths
):
1881 This function computes a pair of x.y lengths,
1882 to change then from pixel to world coordinates.
1884 Lengths should be a NX2 array of (x,y) coordinates, or
1885 a 2-tuple, or sequence of 2-tuples.
1888 return ( asarray ( Lengths
, Float
) / self
. TransformVector
)
1890 def AddObject ( self
, obj
):
1891 # put in a reference to the Canvas, so remove and other stuff can work
1893 if obj
. InForeground
:
1894 self
._ ForeDrawList
. append ( obj
)
1895 self
. UseForeground
= True
1897 self
._ DrawList
. append ( obj
)
1898 self
._ BackgroundDirty
= True
1899 self
. BoundingBoxDirty
= True
1902 def _DrawObjects ( self
, dc
, DrawList
, ScreenDC
, ViewPortBB
, HTdc
= None ):
1904 This is a convenience function;
1905 This function takes the list of objects and draws them to specified
1908 dc
. SetBackground ( self
. BackgroundBrush
)
1911 PanelSize0
, PanelSize1
= self
. PanelSize
# for speed
1912 WorldToPixel
= self
. WorldToPixel
# for speed
1913 ScaleWorldToPixel
= self
. ScaleWorldToPixel
# for speed
1914 Blit
= ScreenDC
. Blit
# for speed
1915 NumBetweenBlits
= self
. NumBetweenBlits
# for speed
1916 for i
, Object
in enumerate ( self
._ ShouldRedraw
( DrawList
, ViewPortBB
)):
1917 Object
._ Draw
( dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
)
1918 if i
% NumBetweenBlits
== 0 :
1919 Blit ( 0 , 0 , PanelSize0
, PanelSize1
, dc
, 0 , 0 )
1922 ## ## This is a way to automatically add a AddObject method for each
1923 ## ## object type This code has been replaced by Leo's code above, so
1924 ## ## that it happens at module init, rather than as needed. The
1925 ## ## primary advantage of this is that dir(FloatCanvas) will have
1926 ## ## them, and docstrings are preserved. Probably more useful
1927 ## ## exceptions if there is a problem, as well.
1928 ## def __getattr__(self, name):
1929 ## if name[:3] == "Add":
1930 ## func=globals()[name[3:]]
1931 ## def AddFun(*args, **kwargs):
1932 ## Object = func(*args, **kwargs)
1933 ## self.AddObject(Object)
1935 ## ## add it to FloatCanvas' dict for future calls.
1936 ## self.__dict__[name] = AddFun
1939 ## raise AttributeError("FloatCanvas has no attribute '%s'"%name)
1941 def _makeFloatCanvasAddMethods (): ## lrk's code for doing this in module __init__
1942 classnames
= [ "Circle" , "Ellipse" , "Rectangle" , "ScaledText" , "Polygon" ,
1943 "Line" , "Text" , "PointSet" ]
1944 for classname
in classnames
:
1945 klass
= globals ()[ classname
]
1946 def getaddshapemethod ( klass
= klass
):
1947 def addshape ( self
, * args
, ** kwargs
):
1948 Object
= klass (* args
, ** kwargs
)
1949 self
. AddObject ( Object
)
1952 addshapemethod
= getaddshapemethod ()
1953 methodname
= "Add" + classname
1954 setattr ( FloatCanvas
, methodname
, addshapemethod
)
1955 docstring
= "Creates %s and adds its reference to the canvas. \n " % classname
1956 docstring
+= "Argument protocol same as %s class" % classname
1958 docstring
+= ", whose docstring is: \n %s " % klass
.__ doc
__
1959 FloatCanvas
.__ dict
__ [ methodname
] .__ doc
__ = docstring
1961 _makeFloatCanvasAddMethods ()