]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/floatcanvas/FloatCanvas.py
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
. Debug
: start
= clock ()
1606 ScreenDC
= wx
. ClientDC ( self
)
1607 ViewPortWorld
= ( self
. PixelToWorld (( 0 , 0 )),
1608 self
. PixelToWorld ( self
. PanelSize
) )
1609 ViewPortBB
= array ( ( minimum
. reduce ( ViewPortWorld
),
1610 maximum
. reduce ( ViewPortWorld
) ) )
1612 dc
. SelectObject ( self
._ Buffer
)
1613 if self
._ BackgroundDirty
or Force
:
1614 #print "Background is Dirty"
1615 dc
. SetBackground ( self
. BackgroundBrush
)
1619 self
._ DrawObjects
( dc
, self
._ DrawList
, ScreenDC
, ViewPortBB
, self
._ HTdc
)
1620 self
._ BackgroundDirty
= False
1622 if self
._ ForeDrawList
:
1623 ## If an object was just added to the Foreground, there might not yet be a buffer
1624 if self
._ ForegroundBuffer
is None :
1625 self
._ ForegroundBuffer
= wx
. EmptyBitmap ( self
. PanelSize
[ 0 ],
1628 dc
= wx
. MemoryDC () ## I got some strange errors (linewidths wrong) if I didn't make a new DC here
1629 dc
. SelectObject ( self
._ ForegroundBuffer
)
1630 dc
. DrawBitmap ( self
._ Buffer
, 0 , 0 )
1631 if self
._ ForegroundHTdc
is None :
1632 self
._ ForegroundHTdc
= wx
. MemoryDC ()
1633 self
._ ForegroundHTdc
. SelectObject ( wx
. EmptyBitmap (
1635 self
. PanelSize
[ 1 ]) )
1637 ## blit the background HT buffer to the foreground HT buffer
1638 self
._ ForegroundHTdc
. Blit ( 0 , 0 ,
1639 self
. PanelSize
[ 0 ], self
. PanelSize
[ 1 ],
1641 self
._ DrawObjects
( dc
,
1645 self
._ ForegroundHTdc
)
1646 ScreenDC
. Blit ( 0 , 0 , self
. PanelSize
[ 0 ], self
. PanelSize
[ 1 ], dc
, 0 , 0 )
1647 ## wx.GetApp().Yield(True)
1648 # If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn
1649 # This seeems out of place, but it works.
1651 ScreenDC
. SetPen ( wx
. Pen ( 'WHITE' , 2 , wx
. SHORT_DASH
))
1652 ScreenDC
. SetBrush ( wx
. TRANSPARENT_BRUSH
)
1653 ScreenDC
. SetLogicalFunction ( wx
. XOR
)
1654 ScreenDC
. DrawRectanglePointSize (* self
. PrevRBBox
)
1655 if self
. Debug
: print "Drawing took %f seconds of CPU time" %( clock ()- start
)
1657 ## Clear the font cache
1658 ## IF you don't do this, the X font server starts to take up Massive amounts of memory
1659 ## This is mostly a problem with very large fonts, that you get with scaled text when zoomed in.
1660 DrawObject
. FontList
= {}
1662 def _ShouldRedraw ( DrawList
, ViewPortBB
): # lrk: adapted code from BBCheck
1663 # lrk: Returns the objects that should be redrawn
1667 for Object
in DrawList
:
1668 BB1
= Object
. BoundingBox
1669 if ( BB1
[ 1 , 0 ] > BB2
[ 0 , 0 ] and BB1
[ 0 , 0 ] < BB2
[ 1 , 0 ] and
1670 BB1
[ 1 , 1 ] > BB2
[ 0 , 1 ] and BB1
[ 0 , 1 ] < BB2
[ 1 , 1 ]):
1671 redrawlist
. append ( Object
)
1673 _ShouldRedraw
= staticmethod ( _ShouldRedraw
)
1676 ## def BBCheck(self, BB1, BB2):
1679 ## BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise
1682 ## if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and
1683 ## (BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ):
1688 def Move ( self
, shift
, CoordType
):
1690 move the image in the window.
1692 shift is an (x,y) tuple, specifying the amount to shift in each direction
1694 It can be in any of three coordinates: Panel, Pixel, World,
1695 specified by the CoordType parameter
1697 Panel coordinates means you want to shift the image by some
1698 fraction of the size of the displaed image
1700 Pixel coordinates means you want to shift the image by some number of pixels
1702 World coordinates mean you want to shift the image by an amount
1703 in Floating point world coordinates
1707 shift
= array ( shift
, Float
)
1708 if CoordType
== 'Panel' : # convert from panel coordinates
1709 shift
= shift
* array ((- 1 , 1 ), Float
) * self
. PanelSize
/ self
. TransformVector
1710 elif CoordType
== 'Pixel' : # convert from pixel coordinates
1711 shift
= shift
/ self
. TransformVector
1712 elif CoordType
== 'World' : # No conversion
1715 raise FloatCanvasException ( 'CoordType must be either "Panel", "Pixel", or "World"' )
1717 self
. ViewPortCenter
= self
. ViewPortCenter
+ shift
1718 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1719 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
) * self
. MapProjectionVector
1720 self
._ BackgroundDirty
= True
1723 def Zoom ( self
, factor
, center
= None ):
1726 Zoom(factor, center) changes the amount of zoom of the image by factor.
1727 If factor is greater than one, the image gets larger.
1728 If factor is less than one, the image gets smaller.
1730 Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming.
1731 If center is not given, the center will stay the same.
1734 self
. Scale
= self
. Scale
* factor
1735 if not center
is None :
1736 self
. ViewPortCenter
= array ( center
, Float
)
1737 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1738 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
) * self
. MapProjectionVector
1739 self
._ BackgroundDirty
= True
1742 def ZoomToBB ( self
, NewBB
= None , DrawFlag
= True ):
1746 Zooms the image to the bounding box given, or to the bounding
1747 box of all the objects on the canvas, if none is given.
1751 if not NewBB
is None :
1754 if self
. BoundingBoxDirty
:
1755 self
._ ResetBoundingBox
()
1756 BoundingBox
= self
. BoundingBox
1757 if not BoundingBox
is None :
1758 self
. ViewPortCenter
= array ((( BoundingBox
[ 0 , 0 ]+ BoundingBox
[ 1 , 0 ])/ 2 ,
1759 ( BoundingBox
[ 0 , 1 ]+ BoundingBox
[ 1 , 1 ])/ 2 ), Float
)
1760 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1761 # Compute the new Scale
1762 BoundingBox
= BoundingBox
* self
. MapProjectionVector
1764 self
. Scale
= min ( abs ( self
. PanelSize
[ 0 ] / ( BoundingBox
[ 1 , 0 ]- BoundingBox
[ 0 , 0 ])),
1765 abs ( self
. PanelSize
[ 1 ] / ( BoundingBox
[ 1 , 1 ]- BoundingBox
[ 0 , 1 ])) )* 0.95
1766 except ZeroDivisionError : # this will happen if the BB has zero width or height
1768 self
. Scale
= ( self
. PanelSize
[ 0 ] / ( BoundingBox
[ 1 , 0 ]- BoundingBox
[ 0 , 0 ]))* 0.95
1769 except ZeroDivisionError :
1771 self
. Scale
= ( self
. PanelSize
[ 1 ] / ( BoundingBox
[ 1 , 1 ]- BoundingBox
[ 0 , 1 ]))* 0.95
1772 except ZeroDivisionError : #zero size! (must be a single point)
1775 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
)* self
. MapProjectionVector
1777 self
._ BackgroundDirty
= True
1780 # Reset the shifting and scaling to defaults when there is no BB
1781 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
1782 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
) # No Projection to start!
1783 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
) # default Transformation
1786 def RemoveObjects ( self
, Objects
):
1787 for Object
in Objects
:
1788 self
. RemoveObject ( Object
, ResetBB
= False )
1789 self
. BoundingBoxDirty
= True
1791 def RemoveObject ( self
, Object
, ResetBB
= True ):
1792 ##fixme: Using the list.remove method is kind of slow
1793 if Object
. InForeground
:
1794 self
._ ForeDrawList
. remove ( Object
)
1796 self
._ DrawList
. remove ( Object
)
1797 self
._ BackgroundDirty
= True
1799 self
. BoundingBoxDirty
= True
1801 def ClearAll ( self
, ResetBB
= True ):
1803 self
._ ForeDrawList
= []
1804 self
._ BackgroundDirty
= True
1805 self
. HitColorGenerator
= None
1806 self
. UseHitTest
= False
1808 self
._ ResetBoundingBox
()
1809 self
. MakeNewBuffers ()
1813 ## def _AddBoundingBox(self,NewBB):
1814 ## if self.BoundingBox is None:
1815 ## self.BoundingBox = NewBB
1816 ## self.ZoomToBB(NewBB,DrawFlag = False)
1818 ## self.BoundingBox = array( ( (min(self.BoundingBox[0,0],NewBB[0,0]),
1819 ## min(self.BoundingBox[0,1],NewBB[0,1])),
1820 ## (max(self.BoundingBox[1,0],NewBB[1,0]),
1821 ## max(self.BoundingBox[1,1],NewBB[1,1]))),
1824 def _getboundingbox ( bboxarray
): # lrk: added this
1826 upperleft
= minimum
. reduce ( bboxarray
[:, 0 ])
1827 lowerright
= maximum
. reduce ( bboxarray
[:, 1 ])
1828 return array (( upperleft
, lowerright
), Float
)
1830 _getboundingbox
= staticmethod ( _getboundingbox
)
1832 def _ResetBoundingBox ( self
):
1833 if self
._ DrawList
or self
._ ForeDrawList
:
1834 bboxarray
= zeros (( len ( self
._ DrawList
)+ len ( self
._ ForeDrawList
), 2 , 2 ), Float
)
1835 i
= - 1 # just in case _DrawList is empty
1836 for ( i
, BB
) in enumerate ( self
._ DrawList
):
1837 bboxarray
[ i
] = BB
. BoundingBox
1838 for ( j
, BB
) in enumerate ( self
._ ForeDrawList
):
1839 bboxarray
[ i
+ j
+ 1 ] = BB
. BoundingBox
1840 self
. BoundingBox
= self
._ getboundingbox
( bboxarray
)
1842 self
. BoundingBox
= None
1843 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
1844 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
)
1845 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
)
1847 self
. BoundingBoxDirty
= False
1849 def PixelToWorld ( self
, Points
):
1851 Converts coordinates from Pixel coordinates to world coordinates.
1853 Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates.
1856 return ((( asarray ( Points
, Float
) - ( self
. PanelSize
/ 2 ))/ self
. TransformVector
) + self
. ViewPortCenter
)
1858 def WorldToPixel ( self
, Coordinates
):
1860 This function will get passed to the drawing functions of the objects,
1861 to transform from world to pixel coordinates.
1862 Coordinates should be a NX2 array of (x,y) coordinates, or
1863 a 2-tuple, or sequence of 2-tuples.
1865 #Note: this can be called by users code for various reasons, so asarray is needed.
1866 return ((( asarray ( Coordinates
, Float
) - self
. ViewPortCenter
)* self
. TransformVector
)+( self
. HalfPanelSize
)). astype ( 'i' )
1868 def ScaleWorldToPixel ( self
, Lengths
):
1870 This function will get passed to the drawing functions of the objects,
1871 to Change a length from world to pixel coordinates.
1873 Lengths should be a NX2 array of (x,y) coordinates, or
1874 a 2-tuple, or sequence of 2-tuples.
1876 return ( ( asarray ( Lengths
, Float
)* self
. TransformVector
) ). astype ( 'i' )
1878 def ScalePixelToWorld ( self
, Lengths
):
1880 This function computes a pair of x.y lengths,
1881 to change then from pixel to world coordinates.
1883 Lengths should be a NX2 array of (x,y) coordinates, or
1884 a 2-tuple, or sequence of 2-tuples.
1887 return ( asarray ( Lengths
, Float
) / self
. TransformVector
)
1889 def AddObject ( self
, obj
):
1890 # put in a reference to the Canvas, so remove and other stuff can work
1892 if obj
. InForeground
:
1893 self
._ ForeDrawList
. append ( obj
)
1894 self
. UseForeground
= True
1896 self
._ DrawList
. append ( obj
)
1897 self
._ BackgroundDirty
= True
1898 self
. BoundingBoxDirty
= True
1901 def _DrawObjects ( self
, dc
, DrawList
, ScreenDC
, ViewPortBB
, HTdc
= None ):
1903 This is a convenience function;
1904 This function takes the list of objects and draws them to specified
1907 dc
. SetBackground ( self
. BackgroundBrush
)
1910 PanelSize0
, PanelSize1
= self
. PanelSize
# for speed
1911 WorldToPixel
= self
. WorldToPixel
# for speed
1912 ScaleWorldToPixel
= self
. ScaleWorldToPixel
# for speed
1913 Blit
= ScreenDC
. Blit
# for speed
1914 NumBetweenBlits
= self
. NumBetweenBlits
# for speed
1915 for i
, Object
in enumerate ( self
._ ShouldRedraw
( DrawList
, ViewPortBB
)):
1916 Object
._ Draw
( dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
)
1917 if i
% NumBetweenBlits
== 0 :
1918 Blit ( 0 , 0 , PanelSize0
, PanelSize1
, dc
, 0 , 0 )
1921 ## ## This is a way to automatically add a AddObject method for each
1922 ## ## object type This code has been replaced by Leo's code above, so
1923 ## ## that it happens at module init, rather than as needed. The
1924 ## ## primary advantage of this is that dir(FloatCanvas) will have
1925 ## ## them, and docstrings are preserved. Probably more useful
1926 ## ## exceptions if there is a problem, as well.
1927 ## def __getattr__(self, name):
1928 ## if name[:3] == "Add":
1929 ## func=globals()[name[3:]]
1930 ## def AddFun(*args, **kwargs):
1931 ## Object = func(*args, **kwargs)
1932 ## self.AddObject(Object)
1934 ## ## add it to FloatCanvas' dict for future calls.
1935 ## self.__dict__[name] = AddFun
1938 ## raise AttributeError("FloatCanvas has no attribute '%s'"%name)
1940 def _makeFloatCanvasAddMethods (): ## lrk's code for doing this in module __init__
1941 classnames
= [ "Circle" , "Ellipse" , "Rectangle" , "ScaledText" , "Polygon" ,
1942 "Line" , "Text" , "PointSet" ]
1943 for classname
in classnames
:
1944 klass
= globals ()[ classname
]
1945 def getaddshapemethod ( klass
= klass
):
1946 def addshape ( self
, * args
, ** kwargs
):
1947 Object
= klass (* args
, ** kwargs
)
1948 self
. AddObject ( Object
)
1951 addshapemethod
= getaddshapemethod ()
1952 methodname
= "Add" + classname
1953 setattr ( FloatCanvas
, methodname
, addshapemethod
)
1954 docstring
= "Creates %s and adds its reference to the canvas. \n " % classname
1955 docstring
+= "Argument protocol same as %s class" % classname
1957 docstring
+= ", whose docstring is: \n %s " % klass
.__ doc
__
1958 FloatCanvas
.__ dict
__ [ methodname
] .__ doc
__ = docstring
1960 _makeFloatCanvasAddMethods ()