From 42463de2678493f48631c42b4d1a7152c2786893 Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Fri, 4 Jun 2004 21:29:41 +0000 Subject: [PATCH] Added a new version (0.8.3) of FloatCanvas from Chris Barker. It's now in a subpackage of wx.lib. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@27637 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- wxPython/demo/FloatCanvas.py | 1288 ++++++++++--- wxPython/demo/Main.py | 1 + wxPython/distrib/DIRLIST | 1 + wxPython/distrib/make_installer.py | 8 + wxPython/docs/CHANGES.txt | 3 + wxPython/setup.py | 1 + wxPython/wx/lib/floatcanvas.py | 1355 -------------- wxPython/wx/lib/floatcanvas/FloatCanvas.py | 1962 ++++++++++++++++++++ wxPython/wx/lib/floatcanvas/NavCanvas.py | 125 ++ wxPython/wx/lib/floatcanvas/Resources.py | 66 + wxPython/wx/lib/floatcanvas/__init__.py | 95 + 11 files changed, 3337 insertions(+), 1568 deletions(-) delete mode 100644 wxPython/wx/lib/floatcanvas.py create mode 100644 wxPython/wx/lib/floatcanvas/FloatCanvas.py create mode 100644 wxPython/wx/lib/floatcanvas/NavCanvas.py create mode 100644 wxPython/wx/lib/floatcanvas/Resources.py create mode 100644 wxPython/wx/lib/floatcanvas/__init__.py diff --git a/wxPython/demo/FloatCanvas.py b/wxPython/demo/FloatCanvas.py index 4b48dac606..94f2eab77c 100644 --- a/wxPython/demo/FloatCanvas.py +++ b/wxPython/demo/FloatCanvas.py @@ -1,148 +1,263 @@ -import wx - -# Stuff to integrate FloatCanvas into wxPython Demo try: import Numeric + import RandomArray haveNumeric = True except ImportError: - haveNumeric = False - + try: + import numarray as Numeric + import numarray.random_array as RandomArray + haveNumeric = True + except ImportError: + haveNumeric = False if not haveNumeric: - errorText = """\ -The FloatCanvas requires the Numeric module: -You can get it at: + errorText = """ +The FloatCanvas requires either the Numeric or Numarray module: +You can get them at: http://sourceforge.net/projects/numpy -""" - def runTest(frame, nb, log): - dlg = wx.MessageDialog( - frame, errorText, 'Sorry', wx.OK | wx.ICON_INFORMATION - ) +NOTE: The Numeric module is substantially faster than numarray for this +purpose, if you have lot's of objects +""" + + def runTest(frame, nb, log): + dlg = wx.MessageDialog(frame, errorText, 'Sorry', wx.OK | + wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() overview = "" - -else: - from wx.lib import floatcanvas - import wx.lib.colourdb - ID_ABOUT_MENU = wx.NewId() - ID_EXIT_MENU = wx.NewId() - ID_ZOOM_TO_FIT_MENU = wx.NewId() - ID_DRAWTEST_MENU = wx.NewId() - ID_LINETEST_MENU = wx.NewId() - ID_DRAWMAP_MENU = wx.NewId() - ID_DRAWMAP2_MENU = wx.NewId() - ID_CLEAR_MENU = wx.NewId() - - - colors = [] - LineStyles = floatcanvas.draw_object.LineStyleList.keys() +else: + StartUpDemo = "all" + if __name__ == "__main__": # parse options if run stand-alone + # check options: + import sys, getopt + optlist, args = getopt.getopt(sys.argv[1:],'l',["local","all","text","map","stext","hit","hitf","animate","speed","temp"]) + + for opt in optlist: + if opt[0] == "--all": + StartUpDemo = "all" + elif opt[0] == "--text": + StartUpDemo = "text" + elif opt[0] == "--map": + StartUpDemo = "map" + elif opt[0] == "--stext": + StartUpDemo = "stext" + elif opt[0] == "--hit": + StartUpDemo = "hit" + elif opt[0] == "--hitf": + StartUpDemo = "hitf" + elif opt[0] == "--animate": + StartUpDemo = "animate" + elif opt[0] == "--speed": + StartUpDemo = "speed" + elif opt[0] == "--temp": + StartUpDemo = "temp" + import wx + import time, random + + def runTest(frame, nb, log): + """ + This method is used by the wxPython Demo Framework for integrating + this demo with the rest. + """ + win = DrawFrame(None, -1, "FloatCanvas Drawing Window",wx.DefaultPosition,(500,500)) + frame.otherWin = win + win.Show(True) + win.DrawTest() + try: + from floatcanvas import NavCanvas, FloatCanvas + except ImportError: # if it's not there locally, try the wxPython lib. + from wx.lib.floatcanvas import NavCanvas, FloatCanvas + import wxPython.lib.colourdb class DrawFrame(wx.Frame): """ - A frame used for the FloatCanvas Demo """ - def __init__(self, parent, id, title, position, size): + + def __init__(self,parent, id,title,position,size): wx.Frame.__init__(self,parent, id,title,position, size) - # Set up the MenuBar - + ## Set up the MenuBar MenuBar = wx.MenuBar() file_menu = wx.Menu() - file_menu.Append(ID_EXIT_MENU, "&Close","Close this frame") - self.Bind(wx.EVT_MENU, self.OnQuit, id=ID_EXIT_MENU) + item = file_menu.Append(-1, "&Close","Close this frame") + self.Bind(wx.EVT_MENU, self.OnQuit, item) MenuBar.Append(file_menu, "&File") draw_menu = wx.Menu() - draw_menu.Append(ID_DRAWTEST_MENU, "&Draw Test","Run a test of drawing random components") - self.Bind(wx.EVT_MENU, self.DrawTest, id=ID_DRAWTEST_MENU) - draw_menu.Append(ID_LINETEST_MENU, "&Line Test","Run a test of drawing random lines") - self.Bind(wx.EVT_MENU, self.LineTest, id=ID_LINETEST_MENU) - draw_menu.Append(ID_DRAWMAP_MENU, "Draw &Map","Run a test of drawing a map") - self.Bind(wx.EVT_MENU, self.DrawMap, id=ID_DRAWMAP_MENU) - draw_menu.Append(ID_CLEAR_MENU, "&Clear","Clear the Canvas") - self.Bind(wx.EVT_MENU, self.Clear, id=ID_CLEAR_MENU) - MenuBar.Append(draw_menu, "&Draw") + item = draw_menu.Append(-1, "&Draw Test","Run a test of drawing random components") + self.Bind(wx.EVT_MENU, self.DrawTest, item) + + item = draw_menu.Append(-1, "&Line Test","Run a test of drawing random lines") + self.Bind(wx.EVT_MENU, self.LineTest, item) + + item = draw_menu.Append(-1, "Draw &Map","Run a test of drawing a map") + self.Bind(wx.EVT_MENU, self.DrawMap, item) + item = draw_menu.Append(-1, "&Text Test","Run a test of text drawing") + self.Bind(wx.EVT_MENU, self.TestText, item) + item = draw_menu.Append(-1, "&ScaledText Test","Run a test of text drawing") + self.Bind(wx.EVT_MENU, self.TestScaledText, item) + item = draw_menu.Append(-1, "&Clear","Clear the Canvas") + self.Bind(wx.EVT_MENU, self.Clear, item) + item = draw_menu.Append(-1, "&Hit Test","Run a test of the hit test code") + self.Bind(wx.EVT_MENU, self.TestHitTest, item) + item = draw_menu.Append(-1, "&Hit Test Foreground","Run a test of the hit test code with a foreground Object") + self.Bind(wx.EVT_MENU, self.TestHitTestForeground, item) + item = draw_menu.Append(-1, "&Animation","Run a test of Animation") + self.Bind(wx.EVT_MENU, self.TestAnimation, item) + item = draw_menu.Append(-1, "&Speed","Run a test of Drawing Speed") + self.Bind(wx.EVT_MENU, self.SpeedTest, item) + MenuBar.Append(draw_menu, "&Tests") + view_menu = wx.Menu() - view_menu.Append(ID_ZOOM_TO_FIT_MENU, "Zoom to &Fit","Zoom to fit the window") - self.Bind(wx.EVT_MENU, self.ZoomToFit, id=ID_ZOOM_TO_FIT_MENU) + item = view_menu.Append(-1, "Zoom to &Fit","Zoom to fit the window") + self.Bind(wx.EVT_MENU, self.ZoomToFit, item) MenuBar.Append(view_menu, "&View") help_menu = wx.Menu() - help_menu.Append(ID_ABOUT_MENU, "&About", + item = help_menu.Append(-1, "&About", "More information About this program") - self.Bind(wx.EVT_MENU, self.OnAbout, id=ID_ABOUT_MENU) + self.Bind(wx.EVT_MENU, self.OnAbout, item) MenuBar.Append(help_menu, "&Help") self.SetMenuBar(MenuBar) - self.CreateStatusBar() - self.SetStatusText("") - - self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) - - # Other event handlers: - self.Bind(wx.EVT_RIGHT_DOWN, self.RightButtonEvent) - + self.CreateStatusBar() # Add the Canvas - self.Canvas = floatcanvas.FloatCanvas(self,-1,(500,500), - ProjectionFun = 'FlatEarth', - Debug = 0, - EnclosingFrame = self, - BackgroundColor = "DARK SLATE BLUE", - UseBackground = 0, - UseToolbar = 1) - self.Show(True) - - self.object_list = [] - + self.Canvas = NavCanvas.NavCanvas(self, + -1, + (500,500), + Debug = 1, + BackgroundColor = "DARK SLATE BLUE") + + wx.EVT_CLOSE(self, self.OnCloseWindow) + + FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) + #FloatCanvas.EVT_LEFT_UP(self.Canvas, self.OnLeftUp ) + + self.EventsAreBound = False + + ## getting all the colors and linestyles for random objects + wxPython.lib.colourdb.updateColourDB() + self.colors = wxPython.lib.colourdb.getColourList() + #self.LineStyles = FloatCanvas.DrawObject.LineStyleList.keys() + + return None - - def RightButtonEvent(self,event): + + def BindAllMouseEvents(self): + if not self.EventsAreBound: + ## Here is how you catch FloatCanvas mouse events + FloatCanvas.EVT_LEFT_DOWN(self.Canvas, self.OnLeftDown ) + FloatCanvas.EVT_LEFT_UP(self.Canvas, self.OnLeftUp ) + FloatCanvas.EVT_LEFT_DCLICK(self.Canvas, self.OnLeftDouble ) + + FloatCanvas.EVT_MIDDLE_DOWN(self.Canvas, self.OnMiddleDown ) + FloatCanvas.EVT_MIDDLE_UP(self.Canvas, self.OnMiddleUp ) + FloatCanvas.EVT_MIDDLE_DCLICK(self.Canvas, self.OnMiddleDouble ) + + FloatCanvas.EVT_RIGHT_DOWN(self.Canvas, self.OnRightDown ) + FloatCanvas.EVT_RIGHT_UP(self.Canvas, self.OnRightUp ) + FloatCanvas.EVT_RIGHT_DCLICK(self.Canvas, self.OnRightDouble ) + + FloatCanvas.EVT_MOUSEWHEEL(self.Canvas, self.OnWheel ) + self.EventsAreBound = True + + def UnBindAllMouseEvents(self): + ## Here is how you catch FloatCanvas mouse events + FloatCanvas.EVT_LEFT_DOWN(self.Canvas, None ) + FloatCanvas.EVT_LEFT_UP(self.Canvas, None ) + FloatCanvas.EVT_LEFT_DCLICK(self.Canvas, None) + + FloatCanvas.EVT_MIDDLE_DOWN(self.Canvas, None ) + FloatCanvas.EVT_MIDDLE_UP(self.Canvas, None ) + FloatCanvas.EVT_MIDDLE_DCLICK(self.Canvas, None ) + + FloatCanvas.EVT_RIGHT_DOWN(self.Canvas, None ) + FloatCanvas.EVT_RIGHT_UP(self.Canvas, None ) + FloatCanvas.EVT_RIGHT_DCLICK(self.Canvas, None ) + + FloatCanvas.EVT_MOUSEWHEEL(self.Canvas, None ) + + self.EventsAreBound = False + + def PrintCoords(self,event): + print "coords are: %s"%(event.Coords,) + print "pixel coords are: %s\n"%(event.GetPosition(),) + + def OnLeftDown(self, event): + print "Left Button has been clicked in DrawFrame" + self.PrintCoords(event) + + def OnLeftUp(self, event): + print "Left up in DrawFrame" + self.PrintCoords(event) + + def OnLeftDouble(self, event): + print "Left Double Click in DrawFrame" + self.PrintCoords(event) + + def OnMiddleDown(self, event): + print "Middle Button clicked in DrawFrame" + self.PrintCoords(event) + + def OnMiddleUp(self, event): + print "Middle Button Up in DrawFrame" + self.PrintCoords(event) + + def OnMiddleDouble(self, event): + print "Middle Button Double clicked in DrawFrame" + self.PrintCoords(event) + + def OnRightDown(self, event): print "Right Button has been clicked in DrawFrame" - print "coords are: %i, %i"%(event.GetX(),event.GetY()) - event.Skip() - + self.PrintCoords(event) + + def OnRightUp(self, event): + print "Right Button Up in DrawFrame" + self.PrintCoords(event) + + def OnRightDouble(self, event): + print "Right Button Double clicked in DrawFrame" + self.PrintCoords(event) + + def OnWheel(self, event): + print "Mouse Wheel Moved in DrawFrame" + self.PrintCoords(event) + + def OnMove(self, event): + """ + Updates the staus bar with the world coordinates + """ + self.SetStatusText("%.2f, %.2f"%tuple(event.Coords)) + def OnAbout(self, event): + print "OnAbout called" + dlg = wx.MessageDialog(self, "This is a small program to demonstrate\n" "the use of the FloatCanvas\n", "About Me", wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() - def SetMode(self,event): - for id in [ID_ZOOM_IN_BUTTON,ID_ZOOM_OUT_BUTTON,ID_MOVE_MODE_BUTTON]: - self.ToolBar.ToggleTool(id,0) - - self.ToolBar.ToggleTool(event.GetId(),1) - - if event.GetId() == ID_ZOOM_IN_BUTTON: - self.Canvas.SetGUIMode("ZoomIn") - - elif event.GetId() == ID_ZOOM_OUT_BUTTON: - self.Canvas.SetGUIMode("ZoomOut") - - elif event.GetId() == ID_MOVE_MODE_BUTTON: - self.Canvas.SetGUIMode("Move") - def ZoomToFit(self,event): self.Canvas.ZoomToBB() def Clear(self,event = None): - self.Canvas.RemoveObjects(self.object_list) - self.object_list = [] + self.UnBindAllMouseEvents() + self.Canvas.ClearAll() + self.Canvas.SetProjectionFun(None) self.Canvas.Draw() def OnQuit(self,event): @@ -151,45 +266,47 @@ else: def OnCloseWindow(self, event): self.Destroy() - def DrawTest(self,event): - wx.GetApp().Yield() - - import random - import RandomArray - + def DrawTest(self,event=None): + wx.GetApp().Yield(True) +# import random +# import RandomArray Range = (-10,10) - + colors = self.colors + + self.BindAllMouseEvents() Canvas = self.Canvas - object_list = self.object_list - - # Random tests of everything: + + Canvas.ClearAll() + Canvas.SetProjectionFun(None) + + ## Random tests of everything: # Rectangles - for i in range(5): + for i in range(3): x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) lw = random.randint(1,5) cf = random.randint(0,len(colors)-1) h = random.randint(1,5) w = random.randint(1,5) - object_list.append(Canvas.AddRectangle(x,y,h,w,LineWidth = lw,FillColor = colors[cf])) + Canvas.AddRectangle(x,y,h,w,LineWidth = lw,FillColor = colors[cf]) - # Ellipses - for i in range(5): + # Ellipses + for i in range(3): x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) lw = random.randint(1,5) cf = random.randint(0,len(colors)-1) h = random.randint(1,5) w = random.randint(1,5) - object_list.append(Canvas.AddEllipse(x,y,h,w,LineWidth = lw,FillColor = colors[cf])) + Canvas.AddEllipse(x,y,h,w,LineWidth = lw,FillColor = colors[cf]) - # Dots - for i in range(5): - x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) - D = random.randint(1,50) - lw = random.randint(1,5) - cf = random.randint(0,len(colors)-1) - cl = random.randint(0,len(colors)-1) - object_list.append(Canvas.AddDot(x,y,D,LineWidth = lw,LineColor = colors[cl],FillColor = colors[cf])) +## # Dots -- Does anyone need this? +## for i in range(5): +## x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) +## D = random.randint(1,50) +## lw = random.randint(1,5) +## cf = random.randint(0,len(colors)-1) +## cl = random.randint(0,len(colors)-1) +## Canvas.AddDot(x,y,D,LineWidth = lw,LineColor = colors[cl],FillColor = colors[cf]) # Circles for i in range(5): @@ -198,8 +315,8 @@ else: lw = random.randint(1,5) cf = random.randint(0,len(colors)-1) cl = random.randint(0,len(colors)-1) - object_list.append(Canvas.AddCircle(x,y,D,LineWidth = lw,LineColor = colors[cl],FillColor = colors[cf])) - self.object_list.append(self.Canvas.AddText("Circle # %i"%(i),x,y,Size = 12,BackGround = None,Position = "cc")) + Canvas.AddCircle(x,y,D,LineWidth = lw,LineColor = colors[cl],FillColor = colors[cf]) + Canvas.AddText("Circle # %i"%(i),x,y,Size = 12,BackgroundColor = None,Position = "cc") # Lines for i in range(5): @@ -210,7 +327,7 @@ else: lw = random.randint(1,10) cf = random.randint(0,len(colors)-1) cl = random.randint(0,len(colors)-1) - self.object_list.append(self.Canvas.AddLine(points, LineWidth = lw, LineColor = colors[cl])) + Canvas.AddLine(points, LineWidth = lw, LineColor = colors[cl]) # Polygons for i in range(3): @@ -221,84 +338,623 @@ else: lw = random.randint(1,6) cf = random.randint(0,len(colors)-1) cl = random.randint(0,len(colors)-1) - self.object_list.append(self.Canvas.AddPolygon(points, - LineWidth = lw, - LineColor = colors[cl], - FillColor = colors[cf], - FillStyle = 'Solid')) - - + Canvas.AddPolygon(points, + LineWidth = lw, + LineColor = colors[cl], + FillColor = colors[cf], + FillStyle = 'Solid') + ## Pointset for i in range(4): points = [] points = RandomArray.uniform(Range[0],Range[1],(100,2)) cf = random.randint(0,len(colors)-1) D = random.randint(1,4) - self.object_list.append(self.Canvas.AddPointSet(points, Color = colors[cf], Diameter = D)) + Canvas.AddPointSet(points, Color = colors[cf], Diameter = D) # Text - String = "Some text" - for i in range(10): + String = "Unscaled text" + for i in range(3): ts = random.randint(10,40) cf = random.randint(0,len(colors)-1) x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) - self.object_list.append(self.Canvas.AddText(String,x,y,Size = ts,ForeGround = colors[cf],Position = "cc")) + Canvas.AddText(String, x, y, Size = ts, Color = colors[cf], Position = "cc") + + # Scaled Text + String = "Scaled text" + for i in range(3): + ts = random.random()*3 + 0.2 + cf = random.randint(0,len(colors)-1) + x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) + Canvas.AddScaledText(String, x, y, Size = ts, Color = colors[cf], Position = "cc") + + Canvas.ZoomToBB() + + def TestAnimation(self,event=None): + """ + + In this test, a relatively complex background is drawn, and + a simple object placed in the foreground is moved over + it. This demonstrates how to use the InForeground attribute + to make an object in the foregorund draw fast, without + having to re-draw the whole background. + + """ + print "Running TestAnimation" + wx.GetApp().Yield(True) + Range = (-10,10) + self.Range = Range + + self.UnBindAllMouseEvents() + Canvas = self.Canvas + + Canvas.ClearAll() + Canvas.SetProjectionFun(None) + + ## Random tests of everything: + colors = self.colors + # Rectangles + for i in range(3): + x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) + lw = random.randint(1,5) + cf = random.randint(0,len(colors)-1) + h = random.randint(1,5) + w = random.randint(1,5) + Canvas.AddRectangle(x,y,h,w,LineWidth = lw,FillColor = colors[cf]) + + # Ellipses + for i in range(3): + x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) + lw = random.randint(1,5) + cf = random.randint(0,len(colors)-1) + h = random.randint(1,5) + w = random.randint(1,5) + Canvas.AddEllipse(x,y,h,w,LineWidth = lw,FillColor = colors[cf]) + + # Circles + for i in range(5): + x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) + D = random.randint(1,5) + lw = random.randint(1,5) + cf = random.randint(0,len(colors)-1) + cl = random.randint(0,len(colors)-1) + Canvas.AddCircle(x,y,D,LineWidth = lw,LineColor = colors[cl],FillColor = colors[cf]) + Canvas.AddText("Circle # %i"%(i),x,y,Size = 12,BackgroundColor = None,Position = "cc") + + # Lines + for i in range(5): + points = [] + for j in range(random.randint(2,10)): + point = (random.randint(Range[0],Range[1]),random.randint(Range[0],Range[1])) + points.append(point) + lw = random.randint(1,10) + cf = random.randint(0,len(colors)-1) + cl = random.randint(0,len(colors)-1) + Canvas.AddLine(points, LineWidth = lw, LineColor = colors[cl]) + + # Polygons + for i in range(3): + points = [] + for j in range(random.randint(2,6)): + point = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) + points.append(point) + lw = random.randint(1,6) + cf = random.randint(0,len(colors)-1) + cl = random.randint(0,len(colors)-1) + Canvas.AddPolygon(points, + LineWidth = lw, + LineColor = colors[cl], + FillColor = colors[cf], + FillStyle = 'Solid') + + # Scaled Text + String = "Scaled text" + for i in range(3): + ts = random.random()*3 + 0.2 + cf = random.randint(0,len(colors)-1) + x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) + Canvas.AddScaledText(String, x, y, Size = ts, Color = colors[cf], Position = "cc") + + + # Now the Foreground Object: + C = Canvas.AddCircle(0,0,7,LineWidth = 2,LineColor = "Black",FillColor = "Red", InForeground = True) + T = Canvas.AddScaledText("Click to Move",0,0, Size = 0.8, Position = 'cc', InForeground = True) + C.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.MoveMe) + C.Text = T + + self.Timer = wx.PyTimer(self.ShowFrame) + self.FrameDelay = 50 # milliseconds + + Canvas.ZoomToBB() + + def ShowFrame(self): + Object = self.MovingObject + Range = self.Range + if self.TimeStep < self.NumTimeSteps: + x,y = Object.XY + if x > Range[1] or x < Range[0]: + self.dx = -self.dx + if y > Range[1] or y < Range[0]: + self.dy = -self.dy + Object.Move( (self.dx,self.dy) ) + Object.Text.Move( (self.dx,self.dy)) + self.Canvas.Draw() + self.TimeStep += 1 + wx.GetApp().Yield(True) + else: + self.Timer.Stop() + + + def MoveMe(self, Object): + self.MovingObject = Object + Range = self.Range + self.dx = random.uniform(Range[0]/4,Range[1]/4) + self.dy = random.uniform(Range[0]/4,Range[1]/4) + #import time + #start = time.time() + self.NumTimeSteps = 500 + self.TimeStep = 1 + self.Timer.Start(self.FrameDelay) + #print "Did %i frames in %f seconds"%(N, (time.time() - start) ) + def TestHitTest(self,event=None): + wx.GetApp().Yield(True) + + self.UnBindAllMouseEvents() + Canvas = self.Canvas + + Canvas.ClearAll() + Canvas.SetProjectionFun(None) + + #Add a HitAble rectangle + w, h = 60, 20 + + dx = 80 + dy = 40 + x,y = 20, 20 + + #Add one that is not HitAble + Canvas.AddRectangle(x, y, w, h, LineWidth = 2) + Canvas.AddText("Not Hit-able", x, y, Position = "bl") + + + x += dx + R = Canvas.AddRectangle(x, y, w, h,LineWidth = 2) + R.Name = "Line Rectangle" + R.HitFill = False + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectGotHit) + Canvas.AddText("Left Click Line", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + + x += dx + color = "Red" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + "Rectangle" + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectGotHit) + Canvas.AddText("Left Click Fill", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x = 20 + y += dy + color = "LightBlue" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + " Rectangle" + R.Bind(FloatCanvas.EVT_FC_RIGHT_DOWN, self.RectGotHit) + Canvas.AddText("Right Click Fill", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "Grey" + R = Canvas.AddEllipse(x, y, w, h,LineWidth = 2,FillColor = color) + R.Name = color +" Ellipse" + R.Bind(FloatCanvas.EVT_FC_RIGHT_DOWN, self.RectGotHit) + Canvas.AddText("Right Click Fill", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "Brown" + R = Canvas.AddCircle(x+dx/2, y+dy/2, dx/4, LineWidth = 2, FillColor = color) + R.Name = color + " Circle" + R.HitFill = True + R.Bind(FloatCanvas.EVT_FC_LEFT_DCLICK, self.RectGotHit) + Canvas.AddText("Left D-Click Fill", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x = 20 + y += dy + color = "Pink" + R = Canvas.AddCircle(x+dx/2, y+dy/2, dx/4, LineWidth = 2,FillColor = color) + R.Name = color + " Circle" + R.Bind(FloatCanvas.EVT_FC_LEFT_UP, self.RectGotHit) + Canvas.AddText("Left Up Fill", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "White" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + " Rectangle" + R.Bind(FloatCanvas.EVT_FC_MIDDLE_DOWN, self.RectGotHit) + Canvas.AddText("Middle Down", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "AQUAMARINE" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + " Rectangle" + R.Bind(FloatCanvas.EVT_FC_MIDDLE_UP, self.RectGotHit) + Canvas.AddText("Middle Up", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x = 20 + y += dy + color = "CORAL" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + " Rectangle" + R.Bind(FloatCanvas.EVT_FC_MIDDLE_DCLICK, self.RectGotHit) + Canvas.AddText("Middle DoubleClick", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "CYAN" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + " Rectangle" + R.Bind(FloatCanvas.EVT_FC_RIGHT_UP, self.RectGotHit) + Canvas.AddText("Right Up", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "LIME GREEN" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + " Rectangle" + R.Bind(FloatCanvas.EVT_FC_RIGHT_DCLICK, self.RectGotHit) + Canvas.AddText("Right Double Click", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x = 20 + y += dy + color = "MEDIUM GOLDENROD" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + R.Bind(FloatCanvas.EVT_FC_RIGHT_DOWN, self.RectGotHitRight) + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectGotHitLeft) + Canvas.AddText("L and R Click", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "SALMON" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + " Rectangle" + R.Bind(FloatCanvas.EVT_FC_ENTER_OBJECT, self.RectMouseOver) + Canvas.AddText("Mouse Enter", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "MEDIUM VIOLET RED" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + R.Bind(FloatCanvas.EVT_FC_LEAVE_OBJECT, self.RectMouseLeave) + Canvas.AddText("Mouse Leave", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x = 20 + y += dy + color = "SKY BLUE" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color) + R.Name = color + R.Bind(FloatCanvas.EVT_FC_ENTER_OBJECT, self.RectMouseOver) + R.Bind(FloatCanvas.EVT_FC_LEAVE_OBJECT, self.RectMouseLeave) + Canvas.AddText("Enter and Leave", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "WHEAT" + R = Canvas.AddRectangle(x, y, w+12, h, LineColor = None, FillColor = color) + R.Name = color + R.Bind(FloatCanvas.EVT_FC_ENTER_OBJECT, self.RectMouseOver) + R.Bind(FloatCanvas.EVT_FC_LEAVE_OBJECT, self.RectMouseLeave) + Canvas.AddText("Mouse Enter&Leave", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "KHAKI" + R = Canvas.AddRectangle(x-12, y, w+12, h, LineColor = None, FillColor = color) + R.Name = color + R.Bind(FloatCanvas.EVT_FC_ENTER_OBJECT, self.RectMouseOver) + R.Bind(FloatCanvas.EVT_FC_LEAVE_OBJECT, self.RectMouseLeave) + Canvas.AddText("Mouse ENter&Leave", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x = 20 + y += dy + L = Canvas.AddLine(( (x, y), (x+10, y+10), (x+w, y+h) ), LineWidth = 2, LineColor = "Red") + L.Name = "A Line" + L.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectGotHitLeft) + Canvas.AddText("Left Down", x, y, Position = "bl") + Canvas.AddText(L.Name, x, y+h, Position = "tl") + + x += dx + color = "SEA GREEN" + Points = Numeric.array(( (x, y), (x, y+2.*h/3), (x+w, y+h), (x+w, y+h/2.), (x + 2.*w/3, y+h/2.), (x + 2.*w/3,y) ), Numeric.Float) + R = Canvas.AddPolygon(Points, LineWidth = 2, FillColor = color) + R.Name = color + " Polygon" + R.Bind(FloatCanvas.EVT_FC_RIGHT_DOWN, self.RectGotHitRight) + Canvas.AddText("RIGHT_DOWN", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x += dx + color = "Red" + Points = Numeric.array(( (x, y), (x, y+2.*h/3), (x+w, y+h), (x+w, y+h/2.), (x + 2.*w/3, y+h/2.), (x + 2.*w/3,y) ), Numeric.Float) + R = Canvas.AddPointSet(Points, Diameter = 4, Color = color) + R.Name = "PointSet" + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.PointSetGotHit) + Canvas.AddText("LEFT_DOWN", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + x = 20 + y += dy + T = Canvas.AddText("Hit-able Text", x, y, Size = 15, Color = "Red", Position = 'tl') + T.Name = "Hit-able Text" + T.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectGotHitLeft) + Canvas.AddText("Left Down", x, y, Position = "bl") + + x += dx + T = Canvas.AddScaledText("Scaled Text", x, y, Size = 1./2*h, Color = "Pink", Position = 'bl') + Canvas.AddPointSet( (x, y), Diameter = 3) + T.Name = "Scaled Text" + T.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectGotHitLeft) + Canvas.AddText("Left Down", x, y, Position = "tl") + + self.Canvas.ZoomToBB() + + def TestHitTestForeground(self,event=None): + print "Running: TestHitTestForeground" + wx.GetApp().Yield(True) + + self.UnBindAllMouseEvents() + Canvas = self.Canvas + + Canvas.ClearAll() + Canvas.SetProjectionFun(None) + + #Add a Hitable rectangle + w, h = 60, 20 + + dx = 80 + dy = 40 + x,y = 20, 20 + + color = "Red" + R = Canvas.AddRectangle(x, y, w, h, LineWidth = 2, FillColor = color, InForeground = False) + R.Name = color + "Rectangle" + R.HitFill = True + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectGotHit) + Canvas.AddText("Left Click Fill", x, y, Position = "bl") + Canvas.AddText(R.Name, x, y+h, Position = "tl") + + ## A set of Rectangles that move together + + ## NOTE: In a real app, it might be better to create a new + ## custom FloatCanvas DrawObject + + self.MovingRects = [] + x += dx + color = "LightBlue" + R = Canvas.AddRectangle(x, y, w/2, h/2, LineWidth = 2, FillColor = color, InForeground = True) + R.HitFill = True + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectMoveLeft) + L = Canvas.AddText("Left", x + w/4, y + h/4, Position = "cc", InForeground = True) + self.MovingRects.extend( (R,L) ) + + x += w/2 + R = Canvas.AddRectangle(x, y, w/2, h/2, LineWidth = 2, FillColor = color, InForeground = True) + R.HitFill = True + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectMoveRight) + L = Canvas.AddText("Right", x + w/4, y + h/4, Position = "cc", InForeground = True) + self.MovingRects.extend( (R,L) ) + + x -= w/2 + y += h/2 + R = Canvas.AddRectangle(x, y, w/2, h/2, LineWidth = 2, FillColor = color, InForeground = True) + R.HitFill = True + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectMoveUp) + L = Canvas.AddText("Up", x + w/4, y + h/4, Position = "cc", InForeground = True) + self.MovingRects.extend( (R,L) ) + + + x += w/2 + R = Canvas.AddRectangle(x, y, w/2, h/2, LineWidth = 2, FillColor = color, InForeground = True) + R.HitFill = True + R.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.RectMoveDown) + L = Canvas.AddText("Down", x + w/4, y + h/4, Position = "cc", InForeground = True) + self.MovingRects.extend( (R,L) ) + + self.Canvas.ZoomToBB() + + def RectMoveLeft(self,Object): + self.MoveRects("left") + + def RectMoveRight(self,Object): + self.MoveRects("right") + + def RectMoveUp(self,Object): + self.MoveRects("up") + + def RectMoveDown(self,Object): + self.MoveRects("down") + + def MoveRects(self, Dir): + for Object in self.MovingRects: + X,Y = Object.XY + if Dir == "left": X -= 10 + elif Dir == "right": X += 10 + elif Dir == "up": Y += 10 + elif Dir == "down": Y -= 10 + Object.SetXY(X,Y) + self.Canvas.Draw() + + + def PointSetGotHit(self, Object): + print Object.Name, "Got Hit\n" + + def RectGotHit(self, Object): + print Object.Name, "Got Hit\n" + + def RectGotHitRight(self, Object): + print Object.Name, "Got Hit With Right\n" + + def RectGotHitLeft(self, Object): + print Object.Name, "Got Hit with Left\n" + + def RectMouseOver(self, Object): + print "Mouse entered:", Object.Name + + def RectMouseLeave(self, Object): + print "Mouse left ", Object.Name + + + def TestText(self, event= None): + wx.GetApp().Yield(True) + + self.BindAllMouseEvents() + Canvas = self.Canvas + Canvas.ClearAll() + Canvas.SetProjectionFun(None) + + x,y = (0, 0) + + ## Add a non-visible rectangle, just to get a Bounding Box + ## Text objects have a zero-size bounding box, because it changes with zoom + Canvas.AddRectangle(-10,-10,20,20,LineWidth = 1, LineColor = None) + + # Text + String = "Some text" +## for i in range(10): +## ts = random.randint(10,40) +## cf = random.randint(0,len(colors)-1) +## x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) + self.Canvas.AddText("Top Left",x,y,Size = 14,Color = "Yellow",BackgroundColor = "Blue", Position = "tl") + self.Canvas.AddText("Bottom Left",x,y,Size = 14,Color = "Cyan",BackgroundColor = "Black",Position = "bl") + self.Canvas.AddText("Top Right",x,y,Size = 14,Color = "Black",BackgroundColor = "Cyan",Position = "tr") + self.Canvas.AddText("Bottom Right",x,y,Size = 14,Color = "Blue",BackgroundColor = "Yellow",Position = "br") + Canvas.AddPointSet((x,y), Color = "White", Diameter = 2) + + x,y = (0, 2) + + Canvas.AddPointSet((x,y), Color = "White", Diameter = 2) + self.Canvas.AddText("Top Center",x,y,Size = 14,Color = "Black",Position = "tc") + self.Canvas.AddText("Bottom Center",x,y,Size = 14,Color = "White",Position = "bc") + + x,y = (0, 4) + + Canvas.AddPointSet((x,y), Color = "White", Diameter = 2) + self.Canvas.AddText("Center Right",x,y,Size = 14,Color = "Black",Position = "cr") + self.Canvas.AddText("Center Left",x,y,Size = 14,Color = "Black",Position = "cl") + + x,y = (0, -2) + + Canvas.AddPointSet((x,y), Color = "White", Diameter = 2) + self.Canvas.AddText("Center Center",x,y,Size = 14,Color = "Black",Position = "cc") + + self.Canvas.AddText("40 Pixels",-10,8,Size = 40) + self.Canvas.AddText("20 Pixels",-10,5,Size = 20) + self.Canvas.AddText("10 Pixels",-10,3,Size = 10) + + self.Canvas.AddText("MODERN Font", -10, 0, Family = wx.MODERN) + self.Canvas.AddText("DECORATIVE Font", -10, -1, Family = wx.DECORATIVE) + self.Canvas.AddText("ROMAN Font", -10, -2, Family = wx.ROMAN) + self.Canvas.AddText("SCRIPT Font", -10, -3, Family = wx.SCRIPT) + self.Canvas.AddText("ROMAN BOLD Font", -10, -4, Family = wx.ROMAN, Weight=wx.BOLD) + self.Canvas.AddText("ROMAN ITALIC BOLD Font", -10, -5, Family = wx.ROMAN, Weight=wx.BOLD, Style=wx.ITALIC) + + # NOTE: this font exists on my Linux box..who knows were else you'll find it! + Font = wx.Font(20, wx.DEFAULT, wx.ITALIC, wx.NORMAL, False, "zapf chancery") + self.Canvas.AddText("zapf chancery Font", -10, -6, Font = Font) + + self.Canvas.ZoomToBB() + + def TestScaledText(self, event= None): + wx.GetApp().Yield(True) + + self.BindAllMouseEvents() + Canvas = self.Canvas + Canvas.ClearAll() + Canvas.SetProjectionFun(None) + + x,y = (0, 0) + + T = Canvas.AddScaledText("Top Left",x,y,Size = 5,Color = "Yellow",BackgroundColor = "Blue", Position = "tl") + T = Canvas.AddScaledText("Bottom Left",x,y,Size = 5,Color = "Cyan",BackgroundColor = "Black",Position = "bl") + T = Canvas.AddScaledText("Top Right",x,y,Size = 5,Color = "Black",BackgroundColor = "Cyan",Position = "tr") + T = Canvas.AddScaledText("Bottom Right",x,y,Size = 5,Color = "Blue",BackgroundColor = "Yellow",Position = "br") + Canvas.AddPointSet((x,y), Color = "Red", Diameter = 4) + + + x,y = (0, 20) + + Canvas.AddScaledText("Top Center",x,y,Size = 7,Color = "Black",Position = "tc") + Canvas.AddScaledText("Bottom Center",x,y,Size = 7,Color = "White",Position = "bc") + Canvas.AddPointSet((x,y), Color = "White", Diameter = 4) + + x,y = (0, -20) + + Canvas.AddScaledText("Center Right",x,y,Size = 9,Color = "Black",Position = "cr") + Canvas.AddScaledText("Center Left",x,y,Size = 9,Color = "Black",Position = "cl") + Canvas.AddPointSet((x,y), Color = "White", Diameter = 4) + + x = -200 + + self.Canvas.AddScaledText("MODERN Font", x, 0, Size = 7, Family = wx.MODERN, Color = (0,0,0)) + self.Canvas.AddScaledText("DECORATIVE Font", x, -10, Size = 7, Family = wx.DECORATIVE, Color = (0,0,1)) + self.Canvas.AddScaledText("ROMAN Font", x, -20, Size = 7, Family = wx.ROMAN) + self.Canvas.AddScaledText("SCRIPT Font", x, -30, Size = 7, Family = wx.SCRIPT) + self.Canvas.AddScaledText("ROMAN BOLD Font", x, -40, Size = 7, Family = wx.ROMAN, Weight=wx.BOLD) + self.Canvas.AddScaledText("ROMAN ITALIC BOLD Font", x, -50, Size = 7, Family = wx.ROMAN, Weight=wx.BOLD, Style=wx.ITALIC) + Canvas.AddPointSet((x,0), Color = "White", Diameter = 4) + + + # NOTE: this font exists on my Linux box..who knows were else you'll find it! + x,y = (-100, 50) + Font = wx.Font(12, wx.DEFAULT, wx.ITALIC, wx.NORMAL, False, "zapf chancery") + T = self.Canvas.AddScaledText("zapf chancery Font", x, y, Size = 20, Font = Font, Position = 'bc') + + x,y = (-50, -50) + Font = wx.Font(12, wx.DEFAULT, wx.ITALIC, wx.NORMAL, False, "bookman") + T = self.Canvas.AddScaledText("Bookman Font", x, y, Size = 8, Font = Font) + self.Canvas.ZoomToBB() def DrawMap(self,event = None): - wx.GetApp().Yield() + wx.GetApp().Yield(True) import os, time + self.BindAllMouseEvents() + ## Test of Actual Map Data - self.Clear() - start = time.clock() + self.Canvas.ClearAll() + self.Canvas.SetProjectionFun("FlatEarth") + #start = time.clock() Shorelines = Read_MapGen(os.path.join("data",'world.dat'),stats = 0) - print "It took %f seconds to load %i shorelines"%(time.clock() - start,len(Shorelines) ) - start = time.clock() + #print "It took %f seconds to load %i shorelines"%(time.clock() - start,len(Shorelines) ) + #start = time.clock() for segment in Shorelines: - self.object_list.append(self.Canvas.AddLine(segment)) - print "It took %f seconds to add %i shorelines"%(time.clock() - start,len(Shorelines) ) - start = time.clock() + self.Canvas.AddLine(segment) + #print "It took %f seconds to add %i shorelines"%(time.clock() - start,len(Shorelines) ) + #start = time.clock() self.Canvas.ZoomToBB() - print "It took %f seconds to draw %i shorelines"%(time.clock() - start,len(Shorelines) ) - - ## def LineTest(self,event = None): - ## wxGetApp().Yield() - ## import os, time - ## import random - ## Range = (-10,10) - ## ## Test of drawing lots of lines - ## self.Clear() - ## start = time.clock() - ## linepoints = [] - ## linecolors = [] - ## linewidths = [] - ## linestyles = [] - ## for i in range(500): - ## points = (random.randint(Range[0],Range[1]), - ## random.randint(Range[0],Range[1])) - ## linepoints.append(points) - ## points = (random.randint(Range[0],Range[1]), - ## random.randint(Range[0],Range[1])) - ## linepoints.append(points) - ## linewidths.append(random.randint(1,10) ) - ## linecolors.append(colors[random.randint(0,len(colors)-1) ]) - ## linestyles.append(LineStyles[random.randint(0, len(LineStyles)-1)]) - - ## self.object_list.append(self.Canvas.AddLineSet(linepoints, LineWidths = linewidths, LineColors = linecolors, LineStyles = linestyles)) - ## print "It took %f seconds to add %i lines"%(time.clock() - start,len(linepoints) ) - ## start = time.clock() - ## self.Canvas.ZoomToBB() - ## print "It took %f seconds to draw %i lines"%(time.clock() - start,len(linepoints) ) + #print "It took %f seconds to draw %i shorelines"%(time.clock() - start,len(Shorelines) ) + def LineTest(self,event = None): - wx.GetApp().Yield() + wx.GetApp().Yield(True) import os, time - import random +# import random + colors = self.colors Range = (-10,10) ## Test of drawing lots of lines - self.Clear() - start = time.clock() + Canvas = self.Canvas + Canvas.ClearAll() + Canvas.SetProjectionFun(None) + #start = time.clock() linepoints = [] linecolors = [] linewidths = [] @@ -311,12 +967,201 @@ else: linewidths.append(random.randint(1,10) ) linecolors.append(random.randint(0,len(colors)-1) ) for (points,color,width) in zip(linepoints,linecolors,linewidths): - self.object_list.append(self.Canvas.AddLine((points[0:2],points[2:4]), LineWidth = width, LineColor = colors[color])) - print "It took %f seconds to add %i lines"%(time.clock() - start,len(linepoints) ) + Canvas.AddLine((points[0:2],points[2:4]), LineWidth = width, LineColor = colors[color]) + #print "It took %f seconds to add %i lines"%(time.clock() - start,len(linepoints) ) + #start = time.clock() + Canvas.ZoomToBB() + #print "It took %f seconds to draw %i lines"%(time.clock() - start,len(linepoints) ) + + def SpeedTest(self,event=None): + wx.GetApp().Yield(True) +# import random +# import RandomArray + BigRange = (-1000,1000) + colors = self.colors + + self.UnBindAllMouseEvents() + Canvas = self.Canvas + + Canvas.ClearAll() + Canvas.SetProjectionFun(None) + + # Lots of Text + String = "Unscaled text" + coords = [] + text = [] + for i in range(5000): + x,y = (random.uniform(BigRange[0],BigRange[1]),random.uniform(BigRange[0],BigRange[1])) + coords.append( (x,y) ) + print "Drawing the Numbers" start = time.clock() - self.Canvas.ZoomToBB() - print "It took %f seconds to draw %i lines"%(time.clock() - start,len(linepoints) ) - + for i in xrange( len(coords) ): + Canvas.AddText("%i"%(i), + coords[i][0], + coords[i][1], + Size = 12, + Position = "cc", + BackgroundColor = "White") + print "It took %s seconds to add the numbers"%(time.clock() - start) + + +## ObjectList = [] + +## print "Building a list of lots of random objects" +## ## Random tests of everything: +## def MakeRange(): +## while True: +## Range = ( random.randint(BigRange[0],BigRange[1]), random.randint(BigRange[0],BigRange[1]) ) +## if abs (Range[0] - Range[1]) < 10: +## continue +## if Range[0] > Range[1]: +## Range = ( Range[1], Range[0] ) +## break +## return Range + +## # Rectangles +## for i in range(300): +## Range = MakeRange() +## x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) +## lw = random.randint(1,5) +## cf = random.randint(0,len(colors)-1) +## h = random.randint(1, Range[1] - Range[0]) +## w = random.randint(1, Range[1] - Range[0]) +## ObjectList.append(FloatCanvas.Rectangle(x,y,h,w,LineWidth = lw,FillColor = colors[cf])) + +## # Ellipses +## for i in range(300): +## Range = MakeRange() +## x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) +## lw = random.randint(1,5) +## cf = random.randint(0,len(colors)-1) +## h = random.randint(1, Range[1] - Range[0]) +## w = random.randint(1, Range[1] - Range[0]) +## ObjectList.append(FloatCanvas.Ellipse(x,y,h,w,LineWidth = lw,FillColor = colors[cf])) + + +## # Circles +## for i in range(500): +## Range = MakeRange() +## x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) +## D = random.randint(1, (Range[1] - Range[0]) / 2) +## lw = random.randint(1,5) +## cf = random.randint(0,len(colors)-1) +## cl = random.randint(0,len(colors)-1) +## ObjectList.append(FloatCanvas.Circle(x,y,D,LineWidth = lw,LineColor = colors[cl],FillColor = colors[cf])) +## #ObjectList.append(FloatCanvas.Text("Circle # %i"%(i),x,y,Size = 12,BackgroundColor = None,Position = "cc")) + +## # Lines +## for i in range(500): +## Range = MakeRange() +## points = [] +## for j in range(random.randint(2,100)): +## point = (random.randint(Range[0],Range[1]),random.randint(Range[0],Range[1])) +## points.append(point) +## lw = random.randint(1,10) +## cf = random.randint(0,len(colors)-1) +## cl = random.randint(0,len(colors)-1) +## ObjectList.append(FloatCanvas.Line(points, LineWidth = lw, LineColor = colors[cl]) ) + +## # Polygons +## for i in range(300): +## Range = MakeRange() +## points = [] +## for j in range(random.randint(2,60)): +## point = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) +## points.append(point) +## lw = random.randint(1,6) +## cf = random.randint(0,len(colors)-1) +## cl = random.randint(0,len(colors)-1) +## ObjectList.append(FloatCanvas.Polygon(points, +## LineWidth = lw, +## LineColor = colors[cl], +## FillColor = colors[cf], +## FillStyle = 'Solid') ) +## random.shuffle(ObjectList) +## print "Adding lots of random objects" +## start = time.clock() +## for Object in ObjectList: +## Canvas.AddObject(Object) +## print "It took %s Seconds to add %i objects "%(time.clock() - start, len(ObjectList) ) + +## ## Pointset +## for i in range(100): +## points = [] +## points = RandomArray.uniform(Range[0],Range[1],(100,2)) +## cf = random.randint(0,len(colors)-1) +## D = random.randint(1,4) +## Canvas.AddPointSet(points, Color = colors[cf], Diameter = D) + + +## # Scaled Text +## String = "Scaled text" +## for i in range(30): +## ts = random.random()*3 + 0.2 +## cf = random.randint(0,len(colors)-1) +## x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) +## Canvas.AddScaledText(String, x, y, Size = ts, Color = colors[cf], Position = "cc") + + Canvas.ZoomToBB() + + + def TempTest(self, event= None): + "Running the Temporary test" + wx.GetApp().Yield(True) + + self.UnBindAllMouseEvents() + Canvas = self.Canvas + Canvas.ClearAll() + Canvas.SetProjectionFun(None) + +# import random +# import RandomArray + Range = (-10,10) + + # Create a random Polygon + points = [] + for j in range(6): + point = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1])) + points.append(point) + Poly = Canvas.AddPolygon(points, + LineWidth = 2, + LineColor = "Black", + FillColor = "LightBlue", + FillStyle = 'Solid') + + Poly.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.SelectPoly) + + self.SelectedPoly = None + self.SelectPoints = [] + self.SelectedPoint = None + + Canvas.ZoomToBB() + + def SelectPoly(self, Object): + print "In SelectPoly" + Canvas = self.Canvas + if Object is self.SelectedPoly: + pass + else: + #fixme: Do something to unselect the old one + self.SelectedPoly = Object + Canvas.RemoveObjects(self.SelectPoints) + self.SelectPoints = [] + # Draw points on the Vertices of the Selected Poly: + for i, point in enumerate(Object.Points): + P = Canvas.AddPointSet(point, Diameter = 6, Color = "Red") + P.VerticeNum = i + P.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.SelectPointHit) + self.SelectPoints.append(P) + #Canvas.ZoomToBB() + Canvas.Draw() + + def SelectPointHit(self, Point): + print "Point Num: %i Hit"%Point.VerticeNum + self.SelectedPoint = Point + + + class DemoApp(wx.App): """ How the demo works: @@ -366,17 +1211,41 @@ else: Chris.Barker@noaa.gov """ + + def __init__(self, *args, **kwargs): + wx.App.__init__(self, *args, **kwargs) def OnInit(self): - global colors - wx.lib.colourdb.updateColourDB() - colors = wx.lib.colourdb.getColourList() - - frame = DrawFrame(None, -1, "FloatCanvas Demo App", - wx.DefaultPosition, (700,700)) + wx.InitAllImageHandlers() + frame = DrawFrame(None, -1, "FloatCanvas Demo App",wx.DefaultPosition,(700,700)) self.SetTopWindow(frame) - + frame.Show() + + ## check to see if the demo is set to start in a particular mode. + if StartUpDemo == "text": + frame.TestText() + if StartUpDemo == "stext": + frame.TestScaledText() + elif StartUpDemo == "all": + frame.DrawTest() + elif StartUpDemo == "map": + frame.DrawMap() + elif StartUpDemo == "hit": + frame.TestHitTest() + elif StartUpDemo == "hitf": + "starting TestHitTestForeground" + frame.TestHitTestForeground() + elif StartUpDemo == "animate": + "starting TestAnimation" + frame.TestAnimation() + elif StartUpDemo == "speed": + "starting SpeedTest" + frame.SpeedTest() + elif StartUpDemo == "temp": + "starting temp Test" + frame.TempTest() + return True def Read_MapGen(filename,stats = 0,AllLines=0): @@ -391,7 +1260,6 @@ else: """ import string - from Numeric import array file = open(filename,'rt') data = file.readlines() data = map(string.strip,data) @@ -401,11 +1269,11 @@ else: for line in data: if line: if line == "# -b": #New segment beginning - if segment: Shorelines.append(array(segment)) + if segment: Shorelines.append(Numeric.array(segment)) segment = [] else: segment.append(map(float,string.split(line))) - if segment: Shorelines.append(array(segment)) + if segment: Shorelines.append(Numeric.array(segment)) if stats: NumSegments = len(Shorelines) @@ -423,49 +1291,43 @@ else: Lines.append(point) Lines.append(point) Lines.append(segment[-1]) - #print Shorelines - #for point in Lines: print point return Lines else: return Shorelines - - - #---------------------------------------------------------------------- - - def runTest(frame, nb, log): - """ - This method is used by the wxPython Demo Framework for integrating - this demo with the rest. - """ - global colors - wx.lib.colourdb.updateColourDB() - colors = wx.lib.colourdb.getColourList() - - win = DrawFrame(None, -1, "FloatCanvas Drawing Window", - wx.DefaultPosition, (500,500)) - frame.otherWin = win - win.Show(True) - - - ## for the wxPython demo: - overview = floatcanvas.FloatCanvas.__doc__ - + try: + import floatcanvas + except ImportError: # if it's not there locally, try the wxPython lib. + from wx.lib import floatcanvas + overview = floatcanvas.__doc__ if __name__ == "__main__": if not haveNumeric: print errorText else: - app = DemoApp(0) - - import wx.lib.colourdb - wx.lib.colourdb.updateColourDB() - colors = wx.lib.colourdb.getColourList() - + app = DemoApp(False)# put in True if you want output to go to it's own window. app.MainLoop() + + + + + + + + + + + + + + + + + + diff --git a/wxPython/demo/Main.py b/wxPython/demo/Main.py index 884b8ee32c..bd0785da86 100644 --- a/wxPython/demo/Main.py +++ b/wxPython/demo/Main.py @@ -32,6 +32,7 @@ _treeList = [ # new stuff ('Recent Additions and Updates', [ 'OGL', + 'FloatCanvas', ]), # managed windows == things with a (optional) caption you can close diff --git a/wxPython/distrib/DIRLIST b/wxPython/distrib/DIRLIST index 2fc48e13ae..0ebfdd7032 100644 --- a/wxPython/distrib/DIRLIST +++ b/wxPython/distrib/DIRLIST @@ -72,6 +72,7 @@ wxPython/wx/lib/editor wxPython/wx/lib/masked wxPython/wx/lib/mixins wxPython/wx/lib/ogl +wxPython/wx/lib/floatcanvas wxPython/wx/py wxPython/wx/py/tests wxPython/wxPython diff --git a/wxPython/distrib/make_installer.py b/wxPython/distrib/make_installer.py index 740c5632ae..c78854abaa 100644 --- a/wxPython/distrib/make_installer.py +++ b/wxPython/distrib/make_installer.py @@ -108,6 +108,7 @@ Source: "wx\lib\editor\*.txt"; DestDir: "{app}\wx\lib\editor"; C Source: "wx\lib\mixins\*.py"; DestDir: "{app}\wx\lib\mixins"; Components: core Source: "wx\lib\masked\*.py"; DestDir: "{app}\wx\lib\masked"; Components: core Source: "wx\lib\ogl\*.py"; DestDir: "{app}\wx\lib\ogl"; Components: core +Source: "wx\lib\floatcanvas\*.py"; DestDir: "{app}\wx\lib\floatcanvas"; Components: core Source: "wx\py\*.py"; DestDir: "{app}\wx\py"; Components: core Source: "wx\py\*.txt"; DestDir: "{app}\wx\py"; Components: core Source: "wx\py\*.ico"; DestDir: "{app}\wx\py"; Components: core @@ -278,6 +279,13 @@ Type: files; Name: "{app}\wx\lib\editor\*.pyc"; Type: files; Name: "{app}\wx\lib\editor\*.pyo"; Type: files; Name: "{app}\wx\lib\mixins\*.pyc"; Type: files; Name: "{app}\wx\lib\mixins\*.pyo"; +Type: files; Name: "{app}\wx\lib\masked\*.pyc"; +Type: files; Name: "{app}\wx\lib\masked\*.pyo"; +Type: files; Name: "{app}\wx\lib\ogl\*.pyc"; +Type: files; Name: "{app}\wx\lib\ogl\*.pyo"; +Type: files; Name: "{app}\wx\lib\floatcanvas\*.pyc"; +Type: files; Name: "{app}\wx\lib\floatcanvas\*.pyo"; + Type: files; Name: "{app}\wx\py\*.pyc"; Type: files; Name: "{app}\wx\py\*.pyo"; Type: files; Name: "{app}\wx\py\tests\*.pyc"; diff --git a/wxPython/docs/CHANGES.txt b/wxPython/docs/CHANGES.txt index cdd70241ee..90d10c0485 100644 --- a/wxPython/docs/CHANGES.txt +++ b/wxPython/docs/CHANGES.txt @@ -109,6 +109,9 @@ Removed the deprecated ErrorDialogs and PythonBitmaps modules. If you were using these in your apps then please join wxPython-dev and assist with a more modern reimplementation. +Added a new version (0.8.3)of FloatCanvas from Chris Barker. It's now +in a subpackage of wx.lib. + diff --git a/wxPython/setup.py b/wxPython/setup.py index 4960145ec4..63c3f23202 100755 --- a/wxPython/setup.py +++ b/wxPython/setup.py @@ -705,6 +705,7 @@ if __name__ == "__main__": 'wx.lib', 'wx.lib.colourchooser', 'wx.lib.editor', + 'wx.lib.floatcanvas', 'wx.lib.masked', 'wx.lib.mixins', 'wx.lib.ogl', diff --git a/wxPython/wx/lib/floatcanvas.py b/wxPython/wx/lib/floatcanvas.py deleted file mode 100644 index 536b161602..0000000000 --- a/wxPython/wx/lib/floatcanvas.py +++ /dev/null @@ -1,1355 +0,0 @@ -import wx - -from Numeric import array,Float,cos,pi,sum,minimum,maximum,Int32 -from time import clock, sleep -import types -import os - -ID_ZOOM_IN_BUTTON = wx.NewId() -ID_ZOOM_OUT_BUTTON = wx.NewId() -ID_ZOOM_TO_FIT_BUTTON = wx.NewId() -ID_MOVE_MODE_BUTTON = wx.NewId() -ID_TEST_BUTTON = wx.NewId() - -ID_ABOUT_MENU = wx.NewId() -ID_EXIT_MENU = wx.NewId() -ID_ZOOM_TO_FIT_MENU = wx.NewId() -ID_DRAWTEST_MENU = wx.NewId() -ID_DRAWMAP_MENU = wx.NewId() -ID_CLEAR_MENU = wx.NewId() - -ID_TEST = wx.NewId() - - -### These are some functions for bitmaps of icons. -import cPickle, zlib - -def GetHandData(): - return cPickle.loads(zlib.decompress( -'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\ -\x01\xc8S\xb6t\x06A(\x1f\x0b\xa0\xa9\x8c\x9e\x1e6\x19\xa0\xa8\x1e\x88\xd4C\ -\x97\xd1\x83\xe8\x80 \x9c2zh\xa6\xc1\x11X\n\xab\x8c\x02\x8a\x0cD!\x92\x12\ -\x98\x8c\x1e\x8a\x8b\xd1d\x14\xf4\x90%\x90LC\xf6\xbf\x1e\xba\xab\x91%\xd0\ -\xdc\x86C\x06\xd9m\xe8!\xaa\x87S\x86\x1a1\xa7\x07\x00v\x0f[\x17' )) - -def GetHandBitmap(): - return wx.BitmapFromXPMData(GetHandData()) - -#---------------------------------------------------------------------- -def GetPlusData(): - return cPickle.loads(zlib.decompress( -'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\ -\x01\xc8S\xb6t\x06A(\x1f\x0b RF\x0f\x08\xb0\xc9@D\xe1r\x08\x19\xb8j=l2`\r\ -\xe82HF\xe9a\xc8\xe8\xe9A\x9c@\x8a\x0c\x0e\xd3p\xbb\x00\x8f\xab\xe1>\xd5\xd3\ -\xc3\x15:P)l!\n\x91\xc2\x1a\xd6`)\xec\xb1\x00\x92\xc2\x11?\xb8e\x88\x8fSt\ -\x19=\x00\x82\x16[\xf7' )) - -def GetPlusBitmap(): - return wx.BitmapFromXPMData(GetPlusData()) - -#---------------------------------------------------------------------- -def GetMinusData(): - return cPickle.loads(zlib.decompress( -'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\ -\x01\xc8S\xb6t\x06A(\x1f\x0b RF\x0f\x08\xb0\xc9@D\xe1r\x08\x19\xb8j=\xa2e\ -\x10\x16@\x99\xc82zz\x10\'\x90"\x83\xc34r\xdc\x86\xf0\xa9\x9e\x1e\xae\xd0\ -\x81Ja\x0bQ\x88\x14\xd6\xb0\x06Ka\x8f\x05\x90\x14\x8e\xf8\xc1-C|\x9c\xa2\xcb\ -\xe8\x01\x00\xed\x0f[\x87' )) - -def GetMinusBitmap(): - return wx.BitmapFromXPMData(GetMinusData()) - -## This is a bunch of stuff for implimenting interactive use: catching -## when objects are clicked on by the mouse, etc. I've made a start, so if -## you are interesed in making that work, let me know, I may have gotten -## it going by then - -#### I probably want a full set of events someday: -## #### mouse over, right click, left click mouse up etc, etc. -## ##FLOATCANVAS_EVENT_LEFT_DOWN = wx.NewEventType() -## ##FLOATCANVAS_EVENT_LEFT_UP = wx.NewEventType() -## ##FLOATCANVAS_EVENT_RIGHT_DOWN = wx.NewEventType() -## ##FLOATCANVAS_EVENT_RIGHT_UP = wx.NewEventType() -## ##FLOATCANVAS_EVENT_MOUSE_OVER = wx.NewEventType() - -##WXFLOATCANVASEVENT = wx.NewEventType() - -##def EVT_WXFLOATCANVASEVENT( window, function ): - -## """Your documentation here""" - -## window.Connect( -1, -1, WXFLOATCANVASEVENT, function ) - -##class wxFloatCanvasObjectEvent(wx.PyCommandEvent): -## def __init__(self, WindowID,Object): -## wx.PyCommandEvent.__init__(self, WXFLOATCANVASEVENT, WindowID) -## self.Object = Object - -## def Clone( self ): -## self.__class__( self.GetId() ) - -##class ColorGenerator: - -## """ An instance of this class generates a unique color each time -## GetNextColor() is called. Someday I will use a proper Python -## generator for this class. - -## The point of this generator is for the hit-test bitmap, each object -## needs to be a unique color. Also, each system can be running a -## different number of colors, and it doesn't appear to be possible to -## have a wxMemDC with a different colordepth as the screen so this -## generates colors far enough apart that they can be distinguished on -## a 16bit screen. Anything less than 16bits won't work. -## """ - -## def __init__(self,depth = 16): -## self.r = 0 -## self.g = 0 -## self.b = 0 -## if depth == 16: -## self.step = 8 -## elif depth >= 24: -## self.step = 1 -## else: -## raise "ColorGenerator does not work with depth = %s"%depth - -## def GetNextColor(self): -## step = self.step -## ##r,g,b = self.r,self.g,self.b -## self.r += step -## if self.r > 255: -## self.r = step -## self.g += step -## if self.g > 255: -## self.g = step -## self.b += step -## if self.b > 255: -## ## fixme: this should be a derived exception -## raise "Too many objects for HitTest" -## return (self.r,self.g,self.b) - - -class draw_object: - """ - This is the base class for all the objects that can be drawn. - - each object has the following properties; (incomplete) - - BoundingBox : is of the form: array((min_x,min_y),(max_x,max_y)) - Pen - Brush - - """ - - def __init__(self,Foreground = 0): - self.Foreground = Foreground - - self._Canvas = None - - # I pre-define all these as class variables to provide an easier - # interface, and perhaps speed things up by caching all the Pens - # and Brushes, although that may not help, as I think wx now - # does that on it's own. Send me a note if you know! - - BrushList = { - ( None,"Transparent") : wx.TRANSPARENT_BRUSH, - ("Blue","Solid") : wx.BLUE_BRUSH, - ("Green","Solid") : wx.GREEN_BRUSH, - ("White","Solid") : wx.WHITE_BRUSH, - ("Black","Solid") : wx.BLACK_BRUSH, - ("Grey","Solid") : wx.GREY_BRUSH, - ("MediumGrey","Solid") : wx.MEDIUM_GREY_BRUSH, - ("LightGrey","Solid") : wx.LIGHT_GREY_BRUSH, - ("Cyan","Solid") : wx.CYAN_BRUSH, - ("Red","Solid") : wx.RED_BRUSH - } - PenList = { - (None,"Transparent",1) : wx.TRANSPARENT_PEN, - ("Green","Solid",1) : wx.GREEN_PEN, - ("White","Solid",1) : wx.WHITE_PEN, - ("Black","Solid",1) : wx.BLACK_PEN, - ("Grey","Solid",1) : wx.GREY_PEN, - ("MediumGrey","Solid",1) : wx.MEDIUM_GREY_PEN, - ("LightGrey","Solid",1) : wx.LIGHT_GREY_PEN, - ("Cyan","Solid",1) : wx.CYAN_PEN, - ("Red","Solid",1) : wx.RED_PEN - } - - FillStyleList = { - "Transparent" : wx.TRANSPARENT, - "Solid" : wx.SOLID, - "BiDiagonalHatch": wx.BDIAGONAL_HATCH, - "CrossDiagHatch" : wx.CROSSDIAG_HATCH, - "FDiagonal_Hatch": wx.FDIAGONAL_HATCH, - "CrossHatch" : wx.CROSS_HATCH, - "HorizontalHatch": wx.HORIZONTAL_HATCH, - "VerticalHatch" : wx.VERTICAL_HATCH - } - - LineStyleList = { - "Solid" : wx.SOLID, - "Transparent": wx.TRANSPARENT, - "Dot" : wx.DOT, - "LongDash" : wx.LONG_DASH, - "ShortDash" : wx.SHORT_DASH, - "DotDash" : wx.DOT_DASH, - } - - def SetBrush(self,FillColor,FillStyle): - if FillColor is None or FillStyle is None: - self.Brush = wx.TRANSPARENT_BRUSH - self.FillStyle = "Transparent" - else: - if not self.BrushList.has_key((FillColor,FillStyle)): - self.BrushList[(FillColor,FillStyle)] = wx.Brush(FillColor,self.FillStyleList[FillStyle]) - self.Brush = self.BrushList[(FillColor,FillStyle)] - - def SetPen(self,LineColor,LineStyle,LineWidth): - if (LineColor is None) or (LineStyle is None): - self.Pen = wx.TRANSPARENT_PEN - self.LineStyle = 'Transparent' - else: - if not self.PenList.has_key((LineColor,LineStyle,LineWidth)): - self.PenList[(LineColor,LineStyle,LineWidth)] = wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle]) - self.Pen = self.PenList[(LineColor,LineStyle,LineWidth)] - - def SetPens(self,LineColors,LineStyles,LineWidths): - """ - This method used when an object could have a list of pens, rather than just one - It is used for LineSet, and perhaps others in the future. - - fixme: this is really kludgy, there has got to be a better way! - - """ - - length = 1 - if type(LineColors) == types.ListType: - length = len(LineColors) - else: - LineColors = [LineColors] - - if type(LineStyles) == types.ListType: - length = len(LineStyles) - else: - LineStyles = [LineStyles] - - if type(LineWidths) == types.ListType: - length = len(LineWidths) - else: - LineWidths = [LineWidths] - - if length > 1: - if len(LineColors) == 1: - LineColors = LineColors*length - if len(LineStyles) == 1: - LineStyles = LineStyles*length - if len(LineWidths) == 1: - LineWidths = LineWidths*length - - self.Pens = [] - for (LineColor,LineStyle,LineWidth) in zip(LineColors,LineStyles,LineWidths): - if LineColor is None or LineStyle is None: - self.Pens.append(wx.TRANSPARENT_PEN) - # what's this for?> self.LineStyle = 'Transparent' - if not self.PenList.has_key((LineColor,LineStyle,LineWidth)): - Pen = wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle]) - self.Pens.append(Pen) - else: - self.Pens.append(self.PenList[(LineColor,LineStyle,LineWidth)]) - if length == 1: - self.Pens = self.Pens[0] - - def PutInBackground(self): - if self._Canvas and self.Foreground: - self._Canvas._TopDrawList.remove(self) - self._Canvas._DrawList.append(self) - self._Canvas._BackgroundDirty = 1 - self.Foreground = 0 - - def PutInForeground(self): - if self._Canvas and (not self.Foreground): - self._Canvas._TopDrawList.append(self) - self._Canvas._DrawList.remove(self) - self._Canvas._BackgroundDirty = 1 - self.Foreground = 1 - - -class Polygon(draw_object): - - """ - - The Polygon class takes a list of 2-tuples, or a NX2 NumPy array of - point coordinates. so that Points[N][0] is the x-coordinate of - point N and Points[N][1] is the y-coordinate or Points[N,0] is the - x-coordinate of point N and Points[N,1] is the y-coordinate for - arrays. - - """ - def __init__(self,Points,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0): - draw_object.__init__(self,Foreground) - self.Points = array(Points,Float) - self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) - - self.LineColor = LineColor - self.LineStyle = LineStyle - self.LineWidth = LineWidth - self.FillColor = FillColor - self.FillStyle = FillStyle - - self.SetPen(LineColor,LineStyle,LineWidth) - self.SetBrush(FillColor,FillStyle) - - - def _Draw(self,dc,WorldToPixel,ScaleFunction): - Points = WorldToPixel(self.Points) - dc.SetPen(self.Pen) - dc.SetBrush(self.Brush) - #dc.DrawPolygon(map(lambda x: (x[0],x[1]), Points.tolist())) - dc.DrawPolygon(Points) - -class PolygonSet(draw_object): - """ - The PolygonSet class takes a Geometry.Polygon object. - so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number! - - it creates a set of line segments, from (x1,y1) to (x2,y2) - - """ - - def __init__(self,PolySet,LineColors,LineStyles,LineWidths,FillColors,FillStyles,Foreground = 0): - draw_object.__init__(self, Foreground) - - ##fixme: there should be some error checking for everything being the right length. - - - self.Points = array(Points,Float) - self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) - - self.LineColors = LineColors - self.LineStyles = LineStyles - self.LineWidths = LineWidths - self.FillColors = FillColors - self.FillStyles = FillStyles - - self.SetPens(LineColors,LineStyles,LineWidths) - - def _Draw(self,dc,WorldToPixel,ScaleFunction): - Points = WorldToPixel(self.Points) - Points.shape = (-1,4) - dc.DrawLineList(Points,self.Pens) - - -class Line(draw_object): - """ - The Line class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates. - so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate - or Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays. - - It will draw a straight line if there are two points, and a polyline if there are more than two. - - """ - def __init__(self,Points,LineColor,LineStyle,LineWidth,Foreground = 0): - draw_object.__init__(self, Foreground) - - self.Points = array(Points,Float) - self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) - - self.LineColor = LineColor - self.LineStyle = LineStyle - self.LineWidth = LineWidth - - self.SetPen(LineColor,LineStyle,LineWidth) - - def SetPoints(self,Points): - self.Points = Points - self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) - if self._Canvas: - # It looks like this shouldn't be private - self._Canvas.BoundingBoxDirty = 1 - - def _Draw(self,dc,WorldToPixel,ScaleFunction): - Points = WorldToPixel(self.Points) - dc.SetPen(self.Pen) - #dc.DrawLines(map(lambda x: (x[0],x[1]), Points.tolist())) - dc.DrawLines(Points) - - -class LineSet(draw_object): - """ - The LineSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates. - so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number! - - it creates a set of line segments, from (x1,y1) to (x2,y2) - - """ - - def __init__(self,Points,LineColors,LineStyles,LineWidths,Foreground = 0): - draw_object.__init__(self, Foreground) - - NumLines = len(Points) / 2 - ##fixme: there should be some error checking for everything being the right length. - - - self.Points = array(Points,Float) - self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) - - self.LineColors = LineColors - self.LineStyles = LineStyles - self.LineWidths = LineWidths - - self.SetPens(LineColors,LineStyles,LineWidths) - - def _Draw(self,dc,WorldToPixel,ScaleFunction): - Points = WorldToPixel(self.Points) - Points.shape = (-1,4) - dc.DrawLineList(Points,self.Pens) - - -class PointSet(draw_object): - """ - The PointSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates. - so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate - or Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays. - - Each point will be drawn the same color and Diameter. The Diameter is in screen points, - not world coordinates. - - """ - def __init__(self,Points,Color,Diameter,Foreground = 0): - draw_object.__init__(self,Foreground) - - self.Points = array(Points,Float) - self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point - self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) - - self.Color = Color - self.Diameter = Diameter - - self.SetPen(Color,"Solid",1) - self.SetBrush(Color,"Solid") - - def SetPoints(self,Points): - self.Points = Points - self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) - if self._Canvas: - # It looks like this shouldn't be private - self._Canvas.BoundingBoxDirty = 1 - - def _Draw(self,dc,WorldToPixel,ScaleFunction): - dc.SetPen(self.Pen) - Points = WorldToPixel(self.Points) - if self.Diameter <= 1: - dc.DrawPointList(Points) - elif self.Diameter <= 2: - # A Little optimization for a diameter2 - point - dc.DrawPointList(Points) - dc.DrawPointList(Points + (1,0)) - dc.DrawPointList(Points + (0,1)) - dc.DrawPointList(Points + (1,1)) - else: - dc.SetBrush(self.Brush) - radius = int(round(self.Diameter/2)) - for (x,y) in Points: - dc.DrawEllipse((x - radius), (y - radius), self.Diameter, self.Diameter) - - - -class Dot(draw_object): - """ - The Dot class takes an x.y coordinate pair, and the Diameter of the circle. - The Diameter is in pixels, so it won't change with zoom. - - Also Fill and line data - - """ - def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0): - draw_object.__init__(self,Foreground) - - self.X = x - self.Y = y - self.Diameter = Diameter - # NOTE: the bounding box does not include the diameter of the dot, as that is in pixel coords. - # If this is a problem, perhaps you should use a circle, instead! - self.BoundingBox = array(((x,y),(x,y)),Float) - - self.LineColor = LineColor - self.LineStyle = LineStyle - self.LineWidth = LineWidth - self.FillColor = FillColor - self.FillStyle = FillStyle - - self.SetPen(LineColor,LineStyle,LineWidth) - self.SetBrush(FillColor,FillStyle) - - def _Draw(self,dc,WorldToPixel,ScaleFunction): - dc.SetPen(self.Pen) - dc.SetBrush(self.Brush) - radius = int(round(self.Diameter/2)) - (X,Y) = WorldToPixel((self.X,self.Y)) - dc.DrawEllipse((X - radius), (Y - radius), self.Diameter, self.Diameter) - - -class Rectangle(draw_object): - def __init__(self,x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0): - draw_object.__init__(self,Foreground) - - self.X = x - self.Y = y - self.Width = width - self.Height = height - self.BoundingBox = array(((x,y),(x+width,y+height)),Float) - self.LineColor = LineColor - self.LineStyle = LineStyle - self.LineWidth = LineWidth - self.FillColor = FillColor - self.FillStyle = FillStyle - - self.SetPen(LineColor,LineStyle,LineWidth) - self.SetBrush(FillColor,FillStyle) - - def _Draw(self,dc,WorldToPixel,ScaleFunction): - (X,Y) = WorldToPixel((self.X,self.Y)) - (Width,Height) = ScaleFunction((self.Width,self.Height)) - - dc.SetPen(self.Pen) - dc.SetBrush(self.Brush) - dc.DrawRectangle(X,Y, Width,Height) - -class Ellipse(draw_object): - def __init__(self,x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0): - draw_object.__init__(self,Foreground) - - self.X = x - self.Y = y - self.Width = width - self.Height = height - self.BoundingBox = array(((x,y),(x+width,y+height)),Float) - self.LineColor = LineColor - self.LineStyle = LineStyle - self.LineWidth = LineWidth - self.FillColor = FillColor - self.FillStyle = FillStyle - - self.SetPen(LineColor,LineStyle,LineWidth) - self.SetBrush(FillColor,FillStyle) - - def _Draw(self,dc,WorldToPixel,ScaleFunction): - (X,Y) = WorldToPixel((self.X,self.Y)) - (Width,Height) = ScaleFunction((self.Width,self.Height)) - - dc.SetPen(self.Pen) - dc.SetBrush(self.Brush) - dc.DrawEllipse(X,Y, Width,Height) - -class Circle(draw_object): - def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0): - draw_object.__init__(self,Foreground) - - self.X = x - self.Y = y - self.Diameter = Diameter - self.BoundingBox = array(((x-Diameter/2,y-Diameter/2),(x+Diameter/2,y+Diameter/2)),Float) - self.LineColor = LineColor - self.LineStyle = LineStyle - self.LineWidth = LineWidth - self.FillColor = FillColor - self.FillStyle = FillStyle - - self.SetPen(LineColor,LineStyle,LineWidth) - self.SetBrush(FillColor,FillStyle) - - def _Draw(self,dc,WorldToPixel,ScaleFunction): - (X,Y) = WorldToPixel((self.X,self.Y)) - (Diameter,dummy) = ScaleFunction((self.Diameter,self.Diameter)) - - dc.SetPen(self.Pen) - dc.SetBrush(self.Brush) - dc.DrawEllipse(X-Diameter/2,Y-Diameter/2, Diameter,Diameter) - -class Text(draw_object): - """ - - This class creates a text object, placed at the coordinates, - x,y. the "Position" argument is a two charactor string, indicating - where in relation to the coordinates the string should be oriented. - - The first letter is: t, c, or b, for top, center and bottom - The second letter is: l, c, or r, for left, center and right - - I've provided arguments for Family, Style, and Weight of font, but - have not yet implimented them, so all text is: wx.SWISS, wx.NORMAL, wx.NORMAL. - I'd love it if someone would impliment that! - - The size is fixed, and does not scale with the drawing. - - """ - - def __init__(self,String,x,y,Size,ForeGround,BackGround,Family,Style,Weight,Underline,Position,Foreground = 0): - draw_object.__init__(self,Foreground) - - self.String = String - self.Size = Size - - self.ForeGround = ForeGround - if BackGround is None: - self.BackGround = None - else: - self.BackGround = BackGround - self.Family = Family - self.Style = Style - self.Weight = Weight - self.Underline = Underline - self.Position = Position - # fixme: this should use the passed in parameters! - self.Font = wx.Font(Size, wx.MODERN, wx.NORMAL, wx.NORMAL, Underline, ) - - self.BoundingBox = array(((x,y),(x,y)),Float) - - self.X = x - self.Y = y - self.x_shift = None - self.y_shift = None - - def _Draw(self,dc,WorldToPixel,ScaleFunction): - (X,Y) = WorldToPixel((self.X,self.Y)) - dc.SetFont(self.Font) - dc.SetTextForeground(self.ForeGround) - if self.BackGround: - dc.SetBackgroundMode(wx.SOLID) - dc.SetTextBackground(self.BackGround) - else: - dc.SetBackgroundMode(wx.TRANSPARENT) - - # compute the shift, and adjust the coordinates, if neccesary - # This had to be put in here, becsuse there is no wx.DC during __init__ - if self.x_shift is None or self.y_shift is None: - if self.Position == 'tl': - x_shift,y_shift = 0,0 - else: - (w,h) = dc.GetTextExtent(self.String) - if self.Position[0] == 't': - y_shift = 0 - elif self.Position[0] == 'c': - y_shift = h/2 - elif self.Position[0] == 'b': - y_shift = h - else: - ##fixme: this should be a real derived exception - raise "Invalid value for Text Object Position Attribute" - if self.Position[1] == 'l': - x_shift = 0 - elif self.Position[1] == 'c': - x_shift = w/2 - elif self.Position[1] == 'r': - x_shift = w - else: - ##fixme: this should be a real derived exception - raise "Invalid value for Text Object Position Attribute" - self.x_shift = x_shift - self.y_shift = y_shift - dc.DrawText(self.String, X-self.x_shift, Y-self.y_shift) - - -#--------------------------------------------------------------------------- - -class FloatCanvas(wx.Panel): - """ - FloatCanvas.py - - This is a high level window for drawing maps and anything else in an - arbitrary coordinate system. - - The goal is to provide a convenient way to draw stuff on the screen - without having to deal with handling OnPaint events, converting to pixel - coordinates, knowing about wxWindows brushes, pens, and colors, etc. It - also provides virtually unlimited zooming and scrolling - - I am using it for two things: - 1) general purpose drawing in floating point coordinates - 2) displaying map data in Lat-long coordinates - - If the projection is set to None, it will draw in general purpose - floating point coordinates. If the projection is set to 'FlatEarth', it - will draw a FlatEarth projection, centered on the part of the map that - you are viewing. You can also pass in your own projection function. - - It is double buffered, so re-draws after the window is uncovered by something - else are very quick. - - It relies on NumPy, which is needed for speed - - Bugs and Limitations: - Lots: patches, fixes welcome - - For Map drawing: It ignores the fact that the world is, in fact, a - sphere, so it will do strange things if you are looking at stuff near - the poles or the date line. so far I don't have a need to do that, so I - havn't bothered to add any checks for that yet. - - Zooming: - I have set no zoom limits. What this means is that if you zoom in really - far, you can get integer overflows, and get wierd results. It - doesn't seem to actually cause any problems other than wierd output, at - least when I have run it. - - Speed: - I have done a couple of things to improve speed in this app. The one - thing I have done is used NumPy Arrays to store the coordinates of the - points of the objects. This allowed me to use array oriented functions - when doing transformations, and should provide some speed improvement - for objects with a lot of points (big polygons, polylines, pointsets). - - The real slowdown comes when you have to draw a lot of objects, because - you have to call the wx.DC.DrawSomething call each time. This is plenty - fast for tens of objects, OK for hundreds of objects, but pretty darn - slow for thousands of objects. - - The solution is to be able to pass some sort of object set to the DC - directly. I've used DC.DrawPointList(Points), and it helped a lot with - drawing lots of points. I havn't got a LineSet type object, so I havn't - used DC.DrawLineList yet. I'd like to get a full set of DrawStuffList() - methods implimented, and then I'd also have a full set of Object sets - that could take advantage of them. I hope to get to it some day. - - Copyright: Christopher Barker - - License: Same as wxPython - - Please let me know if you're using this!!! - - Contact me at: - - Chris.Barker@noaa.gov - - """ - - def __init__(self, parent, id = -1, - size = wx.DefaultSize, - ProjectionFun = None, - BackgroundColor = "WHITE", - Debug = 0, - EnclosingFrame = None, - UseToolbar = 1, - UseBackground = 0, - UseHitTest = 0): - - wx.Panel.__init__( self, parent, id, wx.DefaultPosition, size) - - if ProjectionFun == 'FlatEarth': - self.ProjectionFun = self.FlatEarthProjection - elif type(ProjectionFun) == types.FunctionType: - self.ProjectionFun = ProjectionFun - elif ProjectionFun is None: - self.ProjectionFun = lambda x=None: array( (1,1), Float) - else: - raise('Projectionfun must be either: "FlatEarth", None, or a function that takes the ViewPortCenter and returns a MapProjectionVector') - - self.UseBackground = UseBackground - self.UseHitTest = UseHitTest - - self.NumBetweenBlits = 40 - - ## you can have a toolbar with buttons for zoom-in, zoom-out and - ## move. If you don't use the toolbar, you should provide your - ## own way of navigating the canvas - if UseToolbar: - ## Create the vertical sizer for the toolbar and Panel - box = wx.BoxSizer(wx.VERTICAL) - box.Add(self.BuildToolbar(), 0, wx.ALL | wx.ALIGN_LEFT | wx.GROW, 4) - - self.DrawPanel = wx.Window(self,-1,wx.DefaultPosition,wx.DefaultSize,wx.SUNKEN_BORDER) - box.Add(self.DrawPanel,1,wx.GROW) - - box.Fit(self) - self.SetAutoLayout(True) - self.SetSizer(box) - else: - self.DrawPanel = self - - self.DrawPanel.BackgroundBrush = wx.Brush(BackgroundColor,wx.SOLID) - - self.Debug = Debug - - self.EnclosingFrame = EnclosingFrame - - wx.EVT_PAINT(self.DrawPanel, self.OnPaint) - wx.EVT_SIZE(self.DrawPanel, self.OnSize) - - wx.EVT_LEFT_DOWN(self.DrawPanel, self.LeftButtonEvent) - wx.EVT_LEFT_UP(self.DrawPanel, self.LeftButtonEvent) - wx.EVT_RIGHT_DOWN(self.DrawPanel, self.RightButtonEvent) - wx.EVT_MOTION(self.DrawPanel, self.LeftButtonEvent) - - - self._DrawList = [] - if self.UseBackground: - self._TopDrawList = [] - self.BoundingBox = None - self.BoundingBoxDirty = 0 - self.ViewPortCenter= array( (0,0), Float) - - self.MapProjectionVector = array( (1,1), Float) # No Projection to start! - self.TransformVector = array( (1,-1), Float) # default Transformation - - self.Scale = 1 - - self.GUIMode = None - self.StartRBBox = None - self.PrevRBBox = None - self.StartMove = None - self.PrevMoveBox = None - # called just to make sure everything is initialized - if wx.Platform != "__WXMAC__": - self.OnSize(None) - - - def BuildToolbar(self): - tb = wx.ToolBar(self,-1) - self.ToolBar = tb - - tb.SetToolBitmapSize((23,23)) - - tb.AddTool(ID_ZOOM_IN_BUTTON, GetPlusBitmap(), isToggle=True,shortHelpString = "Zoom In") - wx.EVT_TOOL(self, ID_ZOOM_IN_BUTTON, self.SetMode) - - tb.AddTool(ID_ZOOM_OUT_BUTTON, GetMinusBitmap(), isToggle=True,shortHelpString = "Zoom Out") - wx.EVT_TOOL(self, ID_ZOOM_OUT_BUTTON, self.SetMode) - - tb.AddTool(ID_MOVE_MODE_BUTTON, GetHandBitmap(), isToggle=True,shortHelpString = "Move") - wx.EVT_TOOL(self, ID_MOVE_MODE_BUTTON, self.SetMode) - - tb.AddSeparator() - - tb.AddControl(wx.Button(tb, ID_ZOOM_TO_FIT_BUTTON, "Zoom To Fit",wx.DefaultPosition, wx.DefaultSize)) - wx.EVT_BUTTON(self, ID_ZOOM_TO_FIT_BUTTON, self.ZoomToFit) - - tb.Realize() - return tb - - def SetMode(self,event): - for id in [ID_ZOOM_IN_BUTTON,ID_ZOOM_OUT_BUTTON,ID_MOVE_MODE_BUTTON]: - self.ToolBar.ToggleTool(id,0) - self.ToolBar.ToggleTool(event.GetId(),1) - if event.GetId() == ID_ZOOM_IN_BUTTON: - self.SetGUIMode("ZoomIn") - elif event.GetId() == ID_ZOOM_OUT_BUTTON: - self.SetGUIMode("ZoomOut") - elif event.GetId() == ID_MOVE_MODE_BUTTON: - self.SetGUIMode("Move") - - - def SetGUIMode(self,Mode): - if Mode in ["ZoomIn","ZoomOut","Move",None]: - self.GUIMode = Mode - else: - raise "Not a valid Mode" - - def FlatEarthProjection(self,CenterPoint): - return array((cos(pi*CenterPoint[1]/180),1),Float) - - def LeftButtonEvent(self,event): - if self.EnclosingFrame: - if event.Moving: - position = self.PixelToWorld((event.GetX(),event.GetY())) - self.EnclosingFrame.SetStatusText("%8.3f, %8.3f"%tuple(position)) - if self.GUIMode: - if self.GUIMode == "ZoomIn": - if event.LeftDown(): - self.StartRBBox = (event.GetX(),event.GetY()) - self.PrevRBBox = None - elif event.Dragging() and event.LeftIsDown() and self.StartRBBox: - x0,y0 = self.StartRBBox - x1,y1 = event.GetX(),event.GetY() - w, h = abs(x1-x0),abs(y1-y0) - w = max(w,int(h*self.AspectRatio)) - h = int(w/self.AspectRatio) - x_c, y_c = (x0+x1)/2 , (y0+y1)/2 - dc = wx.ClientDC(self.DrawPanel) - dc.BeginDrawing() - dc.SetPen(wx.Pen('WHITE', 2,wx.SHORT_DASH)) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - dc.SetLogicalFunction(wx.XOR) - if self.PrevRBBox: - dc.DrawRectangle(*self.PrevRBBox) - dc.DrawRectangle(x_c-w/2,y_c-h/2,w,h) - self.PrevRBBox = (x_c-w/2,y_c-h/2,w,h) - dc.EndDrawing() - - elif event.LeftUp() and self.StartRBBox : - self.PrevRBBox = None - EndRBBox = (event.GetX(),event.GetY()) - StartRBBox = self.StartRBBox - # if mouse has moved less that ten pixels, don't use the box. - if abs(StartRBBox[0] - EndRBBox[0]) > 10 and abs(StartRBBox[1] - EndRBBox[1]) > 10: - EndRBBox = self.PixelToWorld(EndRBBox) - StartRBBox = self.PixelToWorld(StartRBBox) - BB = array(((min(EndRBBox[0],StartRBBox[0]), min(EndRBBox[1],StartRBBox[1])), - (max(EndRBBox[0],StartRBBox[0]), max(EndRBBox[1],StartRBBox[1]))),Float) - self.ZoomToBB(BB) - else: - Center = self.PixelToWorld(StartRBBox) - self.Zoom(1.5,Center) - self.StartRBBox = None - - if self.GUIMode == "ZoomOut": - if event.LeftDown(): - Center = self.PixelToWorld((event.GetX(),event.GetY())) - self.Zoom(1/1.5,Center) - elif self.GUIMode == "Move": - if event.LeftDown(): - self.StartMove = array((event.GetX(),event.GetY())) - self.PrevMoveBox = None - elif event.Dragging() and event.LeftIsDown() and self.StartMove: - x_1,y_1 = event.GetX(),event.GetY() - w, h = self.PanelSize - x_tl, y_tl = x_1 - self.StartMove[0], y_1 - self.StartMove[1] - dc = wx.ClientDC(self.DrawPanel) - dc.BeginDrawing() - dc.SetPen(wx.Pen('WHITE', 1,)) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - dc.SetLogicalFunction(wx.XOR) - if self.PrevMoveBox: - dc.DrawRectangle(*self.PrevMoveBox) - dc.DrawRectangle(x_tl,y_tl,w,h) - self.PrevMoveBox = (x_tl,y_tl,w,h) - dc.EndDrawing() - - elif event.LeftUp() and self.StartMove: - self.PrevMoveBox = None - StartMove = self.StartMove - EndMove = array((event.GetX(),event.GetY())) - if sum((StartMove-EndMove)**2) > 16: - self.Move(StartMove-EndMove,'Pixel') - self.StartMove = None - - def RightButtonEvent(self,event): - if self.GUIMode: - if self.GUIMode == "ZoomIn": - Center = self.PixelToWorld((event.GetX(),event.GetY())) - self.Zoom(1/1.5,Center) - elif self.GUIMode == "ZoomOut": - Center = self.PixelToWorld((event.GetX(),event.GetY())) - self.Zoom(1.5,Center) - else: - event.Skip() - event.Skip() - - def MakeNewBuffers(self): - # Make new offscreen bitmap: - self._Buffer = wx.EmptyBitmap(self.PanelSize[0],self.PanelSize[1]) - if self.UseBackground: - self._BackBuffer = wx.EmptyBitmap(self.PanelSize[0],self.PanelSize[1]) - self._BackgroundDirty = 1 - else: - pass - - def OnSize(self,event): - self.PanelSize = array(self.DrawPanel.GetClientSizeTuple(),Int32) - try: - self.AspectRatio = self.PanelSize[0]/self.PanelSize[1] - except ZeroDivisionError: - self.AspectRatio = 1.0 - self.MakeNewBuffers() - self.Draw() - - def OnPaint(self, event): - #dc = wx.BufferedPaintDC(self.DrawPanel, self._Buffer) - dc = wx.PaintDC(self.DrawPanel) - dc.DrawBitmap(self._Buffer, 0,0) - - def Draw(self): - """ - The Draw method gets pretty complicated because of all the buffers - - There is a main buffer set up to double buffer the screen, so - you can get quick re-draws when the window gets uncovered. - - If self.UseBackground is set, and an object is set up with the - "ForeGround" flag, then it gets drawn to the screen after blitting - the background. This is done so that you can have a complicated - background, but have something changing on the foreground, - without having to wait for the background to get re-drawn. This - can be used to support simple animation, for instance. - - """ - if self.Debug: start = clock() - ScreenDC = wx.ClientDC(self.DrawPanel) - ViewPortWorld = ( self.PixelToWorld((0,0)), self.PixelToWorld(self.PanelSize) ) - ViewPortBB = array( ( minimum.reduce(ViewPortWorld), maximum.reduce(ViewPortWorld) ) ) - if self.UseBackground: - dc = wx.MemoryDC() - dc.SelectObject(self._BackBuffer) - dc.SetBackground(self.DrawPanel.BackgroundBrush) - if self._DrawList: - if self._BackgroundDirty: - dc.BeginDrawing() - dc.Clear() - i = 0 - for Object in self._DrawList: - if self.BBCheck(Object.BoundingBox,ViewPortBB): - #print "object is in Bounding Box" - i+=1 - Object._Draw(dc,self.WorldToPixel,self.ScaleFunction) - if i % self.NumBetweenBlits == 0: - w,h = self.PanelSize - ScreenDC.Blit(0, 0, w,h, dc, 0, 0) - dc.EndDrawing() - else: - dc.Clear() - self._BackgroundDirty = 0 - dc.SelectObject(self._Buffer) - dc.BeginDrawing() - ##Draw Background on Main Buffer: - dc.DrawBitmap(self._BackBuffer,0,0) - #Draw the OnTop stuff - i = 0 - for Object in self._TopDrawList: - i+=1 - Object._Draw(dc,self.WorldToPixel,self.ScaleFunction) - if i % self.NumBetweenBlits == 0: - w, h = self.PanelSize - ScreenDC.Blit(0, 0, w,h, dc, 0, 0) - dc.EndDrawing() - else: # not using a Background DC - dc = wx.MemoryDC() - dc.SelectObject(self._Buffer) - dc.SetBackground(self.DrawPanel.BackgroundBrush) - if self._DrawList: - dc.BeginDrawing() - dc.Clear() - i = 0 - for Object in self._DrawList: - if self.BBCheck(Object.BoundingBox,ViewPortBB): - #print "object is in Bounding Box" - i+=1 - Object._Draw(dc,self.WorldToPixel,self.ScaleFunction) - if i % self.NumBetweenBlits == 0: - w, h = self.PanelSize - ScreenDC.Blit(0, 0, w, h, dc, 0, 0) - dc.EndDrawing() - else: - dc.Clear() - # now refresh the screen - #ScreenDC.DrawBitmap(self._Buffer,0,0) #NOTE: uisng DrawBitmap didn't work right on MSW - w, h = self.PanelSize - ScreenDC.Blit(0, 0, w, h, dc, 0, 0) - - # If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn - if self.PrevRBBox: - ScreenDC.SetPen(wx.Pen('WHITE', 2,wx.SHORT_DASH)) - ScreenDC.SetBrush(wx.TRANSPARENT_BRUSH) - ScreenDC.SetLogicalFunction(wx.XOR) - ScreenDC.DrawRectangle(*self.PrevRBBox) - elif self.PrevMoveBox: - ScreenDC.SetPen(wx.Pen('WHITE', 1,)) - ScreenDC.SetBrush(wx.TRANSPARENT_BRUSH) - ScreenDC.SetLogicalFunction(wx.XOR) - ScreenDC.DrawRectangle(*self.PrevMoveBox) - if self.Debug: print "Drawing took %f seconds of CPU time"%(clock()-start) - - def BBCheck(self, BB1, BB2): - """ - - BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise - - """ - if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and - (BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ): - return True - else: - return False - - def Move(self,shift,CoordType): - """ - move the image in the window. - - shift is an (x,y) tuple, specifying the amount to shift in each direction - - It can be in any of three coordinates: Panel, Pixel, World, - specified by the CoordType parameter - - Panel coordinates means you want to shift the image by some - fraction of the size of the displaed image - - Pixel coordinates means you want to shift the image by some number of pixels - - World coordinates meand you want to shift the image by an amount - in Floating point world coordinates - - """ - - shift = array(shift,Float) - if CoordType == 'Panel':# convert from panel coordinates - shift = shift * array((-1,1),Float) *self.PanelSize/self.TransformVector - elif CoordType == 'Pixel': # convert from pixel coordinates - shift = shift/self.TransformVector - elif CoordType == 'World': # No conversion - pass - else: - raise 'CoordType must be either "Panel", "Pixel", or "World"' - - self.ViewPortCenter = self.ViewPortCenter + shift - self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter) - self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector - self._BackgroundDirty = 1 - self.Draw() - - def Zoom(self,factor,center = None): - - """ - Zoom(factor, center) changes the amount of zoom of the image by factor. - If factor is greater than one, the image gets larger. - If factor is less than one, the image gets smaller. - - Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming. - If center is not given, the center will stay the same. - - """ - self.Scale = self.Scale*factor - if center: - self.ViewPortCenter = array(center,Float) - self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter) - self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector - self._BackgroundDirty = 1 - self.Draw() - - def ZoomToFit(self,event): - self.ZoomToBB() - - def ZoomToBB(self,NewBB = None,DrawFlag = 1): - - """ - - Zooms the image to the bounding box given, or to the bounding - box of all the objects on the canvas, if none is given. - - """ - - if NewBB: - BoundingBox = NewBB - else: - if self.BoundingBoxDirty: - self._ResetBoundingBox() - BoundingBox = self.BoundingBox - if BoundingBox: - self.ViewPortCenter = array(((BoundingBox[0,0]+BoundingBox[1,0])/2, - (BoundingBox[0,1]+BoundingBox[1,1])/2 ),Float) - self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter) - # Compute the new Scale - BoundingBox = BoundingBox * self.MapProjectionVector - try: - self.Scale = min((self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0])), - (self.PanelSize[1] / (BoundingBox[1,1]-BoundingBox[0,1])))*0.95 - except ZeroDivisionError: # this will happen if the BB has zero width or height - try: #width - self.Scale = (self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0]))*0.95 - except ZeroDivisionError: - try: # height - self.Scale = (self.PanelSize[1] / (BoundingBox[1,1]-BoundingBox[0,1]))*0.95 - except ZeroDivisionError: #zero size! (must be a single point) - self.Scale = 1 - - self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector - if DrawFlag: - self._BackgroundDirty = 1 - self.Draw() - - def RemoveObjects(self,Objects): - for Object in Objects: - self.RemoveObject(Object,ResetBB = 0) - self.BoundingBoxDirty = 1 - - def RemoveObject(self,Object,ResetBB = 1): - if Object.Foreground: - self._TopDrawList.remove(Object) - else: - self._DrawList.remove(Object) - self._BackgroundDirty = 1 - - if ResetBB: - self.BoundingBoxDirty = 1 - - def Clear(self, ResetBB = True): - self._DrawList = [] - if self.UseBackground: - self._TopDrawList = [] - self._BackgroundDirty = 1 - if ResetBB: - self._ResetBoundingBox() - - def _AddBoundingBox(self,NewBB): - if self.BoundingBox is None: - self.BoundingBox = NewBB - self.ZoomToBB(NewBB,DrawFlag = 0) - else: - self.BoundingBox = array(((min(self.BoundingBox[0,0],NewBB[0,0]), - min(self.BoundingBox[0,1],NewBB[0,1])), - (max(self.BoundingBox[1,0],NewBB[1,0]), - max(self.BoundingBox[1,1],NewBB[1,1]))),Float) - def _ResetBoundingBox(self): - # NOTE: could you remove an item without recomputing the entire bounding box? - self.BoundingBox = None - if self._DrawList: - self.BoundingBox = self._DrawList[0].BoundingBox - for Object in self._DrawList[1:]: - self._AddBoundingBox(Object.BoundingBox) - if self.UseBackground: - for Object in self._TopDrawList: - self._AddBoundingBox(Object.BoundingBox) - if self.BoundingBox is None: - self.ViewPortCenter= array( (0,0), Float) - self.TransformVector = array( (1,-1), Float) - self.MapProjectionVector = array( (1,1), Float) - self.Scale = 1 - self.BoundingBoxDirty = 0 - - def PixelToWorld(self,Points): - """ - Converts coordinates from Pixel coordinates to world coordinates. - - Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates. - - """ - return (((array(Points,Float) - (self.PanelSize/2))/self.TransformVector) + self.ViewPortCenter) - - def WorldToPixel(self,Coordinates): - """ - This function will get passed to the drawing functions of the objects, - to transform from world to pixel coordinates. - Coordinates should be a NX2 array of (x,y) coordinates, or - a 2-tuple, or sequence of 2-tuples. - """ - return (((array(Coordinates,Float) - self.ViewPortCenter)*self.TransformVector)+(self.PanelSize/2)).astype('i') - - def ScaleFunction(self,Lengths): - """ - This function will get passed to the drawing functions of the objects, - to Change a length from world to pixel coordinates. - - Lengths should be a NX2 array of (x,y) coordinates, or - a 2-tuple, or sequence of 2-tuples. - """ - return (array(Lengths,Float)*self.TransformVector).astype('i') - - - ## This is a set of methods that add objects to the Canvas. It kind - ## of seems like a lot of duplication, but I wanted to be able to - ## instantiate the draw objects separatley form adding them, but - ## also to be able to do add one oin one step. I'm open to better - ## ideas... - def AddRectangle(self,x,y,width,height, - LineColor = "Black", - LineStyle = "Solid", - LineWidth = 1, - FillColor = None, - FillStyle = "Solid", - Foreground = 0): - Object = Rectangle(x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground) - self.AddObject(Object) - return Object - - def AddEllipse(self,x,y,width,height, - LineColor = "Black", - LineStyle = "Solid", - LineWidth = 1, - FillColor = None, - FillStyle = "Solid", - Foreground = 0): - Object = Ellipse(x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground) - self.AddObject(Object) - return Object - - def AddCircle(self,x,y,Diameter, - LineColor = "Black", - LineStyle = "Solid", - LineWidth = 1, - FillColor = None, - FillStyle = "Solid", - Foreground = 0): - Object = Circle(x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground) - self.AddObject(Object) - return Object - - def AddDot(self,x,y,Diameter, - LineColor = "Black", - LineStyle = "Solid", - LineWidth = 1, - FillColor = None, - FillStyle = "Solid", - Foreground = 0): - Object = Dot(x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground) - self.AddObject(Object) - return Object - - def AddPolygon(self,Points, - LineColor = "Black", - LineStyle = "Solid", - LineWidth = 1, - FillColor = None, - FillStyle = "Solid", - Foreground = 0): - - Object = Polygon(Points,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground) - self.AddObject(Object) - return Object - - def AddLine(self,Points, - LineColor = "Black", - LineStyle = "Solid", - LineWidth = 1, - Foreground = 0): - - Object = Line(Points,LineColor,LineStyle,LineWidth,Foreground) - self.AddObject(Object) - return Object - - def AddLineSet(self,Points, - LineColors = "Black", - LineStyles = "Solid", - LineWidths = 1, - Foreground = 0): - - Object = LineSet(Points,LineColors,LineStyles,LineWidths,Foreground) - self.AddObject(Object) - return Object - - def AddPointSet(self,Points, - Color = "Black", - Diameter = 1, - Foreground = 0): - - Object = PointSet(Points,Color,Diameter,Foreground) - self.AddObject(Object) - return Object - - def AddText(self,String,x,y, - Size = 20, - ForeGround = 'Black', - BackGround = None, - Family = 'Swiss', - Style = 'Normal', - Weight = 'Normal', - Underline = 0, - Position = 'tl', - Foreground = 0): - Object = Text(String,x,y,Size,ForeGround,BackGround,Family,Style,Weight,Underline,Position,Foreground) - self.AddObject(Object) - return Object - - def AddObject(self,obj): - # put in a reference to the Canvas, so remove and other stuff can work - obj._Canvas = self - if obj.Foreground and self.UseBackground: - self._TopDrawList.append(obj) - else: - self._DrawList.append(obj) - self._backgrounddirty = 1 - self._AddBoundingBox(obj.BoundingBox) - return None - - - - - - - diff --git a/wxPython/wx/lib/floatcanvas/FloatCanvas.py b/wxPython/wx/lib/floatcanvas/FloatCanvas.py new file mode 100644 index 0000000000..83865fbc34 --- /dev/null +++ b/wxPython/wx/lib/floatcanvas/FloatCanvas.py @@ -0,0 +1,1962 @@ + +try: + from Numeric import array,asarray,Float,cos,pi,sum,minimum,maximum,Int32,zeros +except ImportError: + from numarray import array, asarray, Float, cos, pi, sum, minimum, maximum, Int32, zeros + +from time import clock, sleep + +import wx + +import types +import os + +import Resources + +## A global variable to hold the Pixels per inch that wxWindows thinks is in use +## This is used for scaling fonts. +## This can't be computed on module __init__, because a wx.App might not have iniitalized yet. +global ScreenPPI + +## a custom Exceptions: + +class FloatCanvasException(Exception): + pass + +## All the mouse events +#EVT_FC_ENTER_WINDOW = wx.NewEventType() +#EVT_FC_LEAVE_WINDOW = wx.NewEventType() +EVT_FC_LEFT_DOWN = wx.NewEventType() +EVT_FC_LEFT_UP = wx.NewEventType() +EVT_FC_LEFT_DCLICK = wx.NewEventType() +EVT_FC_MIDDLE_DOWN = wx.NewEventType() +EVT_FC_MIDDLE_UP = wx.NewEventType() +EVT_FC_MIDDLE_DCLICK = wx.NewEventType() +EVT_FC_RIGHT_DOWN = wx.NewEventType() +EVT_FC_RIGHT_UP = wx.NewEventType() +EVT_FC_RIGHT_DCLICK = wx.NewEventType() +EVT_FC_MOTION = wx.NewEventType() +EVT_FC_MOUSEWHEEL = wx.NewEventType() +## these two are for the hit-test stuff, I never make them real Events +EVT_FC_ENTER_OBJECT = wx.NewEventType() +EVT_FC_LEAVE_OBJECT = wx.NewEventType() + +#def EVT_ENTER_WINDOW( window, function ): +# window.Connect( -1, -1, EVT_FC_ENTER_WINDOW, function ) +#def EVT_LEAVE_WINDOW( window, function ): +# window.Connect( -1, -1,EVT_FC_LEAVE_WINDOW , function ) +def EVT_LEFT_DOWN( window, function ): + window.Connect( -1, -1,EVT_FC_LEFT_DOWN , function ) +def EVT_LEFT_UP( window, function ): + window.Connect( -1, -1,EVT_FC_LEFT_UP , function ) +def EVT_LEFT_DCLICK ( window, function ): + window.Connect( -1, -1,EVT_FC_LEFT_DCLICK , function ) +def EVT_MIDDLE_DOWN ( window, function ): + window.Connect( -1, -1,EVT_FC_MIDDLE_DOWN , function ) +def EVT_MIDDLE_UP ( window, function ): + window.Connect( -1, -1,EVT_FC_MIDDLE_UP , function ) +def EVT_MIDDLE_DCLICK ( window, function ): + window.Connect( -1, -1,EVT_FC_MIDDLE_DCLICK , function ) +def EVT_RIGHT_DOWN ( window, function ): + window.Connect( -1, -1,EVT_FC_RIGHT_DOWN , function ) +def EVT_RIGHT_UP( window, function ): + window.Connect( -1, -1,EVT_FC_RIGHT_UP , function ) +def EVT_RIGHT_DCLICK( window, function ): + window.Connect( -1, -1,EVT_FC_RIGHT_DCLICK , function ) +def EVT_MOTION( window, function ): + window.Connect( -1, -1,EVT_FC_MOTION , function ) +def EVT_MOUSEWHEEL( window, function ): + window.Connect( -1, -1,EVT_FC_MOUSEWHEEL , function ) + +class MouseEvent(wx.PyCommandEvent): + + """ + + This event class takes a regular wxWindows mouse event as a parameter, + and wraps it so that there is access to all the original methods. This + is similar to subclassing, but you can't subclass a wxWindows event + + The goal is to be able to it just like a regular mouse event. + + It adds the method: + + GetCoords() , which returns and (x,y) tuple in world coordinates. + + Another differnce is that it is a CommandEvent, which propagates up + the window hierarchy until it is handled. + + """ + + def __init__(self, EventType, NativeEvent, WinID, Coords = None): + wx.PyCommandEvent.__init__(self) + + self.SetEventType( EventType ) + self._NativeEvent = NativeEvent + self.Coords = Coords + + def SetCoords(self,Coords): + self.Coords = Coords + + def GetCoords(self): + return self.Coords + + def __getattr__(self, name): + #return eval(self.NativeEvent.__getattr__(name) ) + return getattr(self._NativeEvent, name) + +#### ColorGEnerator class is now obsolete. I'm using a python generator function instead. +##class ColorGenerator: + +## """ + +## An instance of this class generates a unique color each time +## GetNextColor() is called. Someday I will use a proper Python +## generator for this class. + +## The point of this generator is for the hit-test bitmap, each object +## needs to be a unique color. Also, each system can be running a +## different number of colors, and it doesn't appear to be possible to +## have a wxMemDC with a different colordepth as the screen so this +## generates colors far enough apart that they can be distinguished on +## a 16bit screen. Anything less than 16bits won't work. It could, but +## I havn't written the code that way. You also wouldn't get many +## distict colors + +## """ + +## def __init__(self): +## import sys +## ## figure out the color depth of the screen +## ## for some bizare reason, thisdoesn't work on OS-X +## if sys.platform == 'darwin': +## depth = 24 +## else: +## b = wx.EmptyBitmap(1,1) +## depth = b.GetDepth() +## self.r = 0 +## self.g = 0 +## self.b = 0 +## if depth == 16: +## self.step = 8 +## elif depth >= 24: +## self.step = 1 +## else: +## raise FloatCanvasException("ColorGenerator does not work with depth = %s"%depth ) + +## def GetNextColor(self): +## step = self.step +## ##r,g,b = self.r,self.g,self.b +## self.r += step +## if self.r > 255: +## self.r = step +## self.g += step +## if self.g > 255: +## self.g = step +## self.b += step +## if self.b > 255: +## ## fixme: this should be a derived exception +## raise FloatCanvasException("Too many objects in colorgenerator for HitTest") +## return (self.r,self.g,self.b) + +## def Reset(self): +## self.r = 0 +## self.g = 0 +## self.b = 0 + +def cycleidxs(indexcount, maxvalue, step): + if indexcount == 0: + yield () + else: + for idx in xrange(0, maxvalue, step): + for tail in cycleidxs(indexcount - 1, maxvalue, step): + yield (idx, ) + tail + +def colorGenerator(): + import sys + if sys.platform == 'darwin': + depth = 24 + else: + b = wx.EmptyBitmap(1,1) + depth = b.GetDepth() + if depth == 16: + step = 8 + elif depth >= 24: + step = 1 + else: + raise "ColorGenerator does not work with depth = %s" % depth + return cycleidxs(indexcount=3, maxvalue=256, step=step) + + +#### I don't know if the Set objects are useful, beyond the pointset object +#### The problem is that when zoomed in, the BB is checked to see whether to draw the object. +#### A Set object can defeat this + +##class ObjectSetMixin: +## """ +## A mix-in class for draw objects that are sets of objects + +## It contains methods for setting lists of pens and brushes + +## """ +## def SetPens(self,LineColors,LineStyles,LineWidths): +## """ +## This method used when an object could have a list of pens, rather than just one +## It is used for LineSet, and perhaps others in the future. + +## fixme: this should be in a mixin + +## fixme: this is really kludgy, there has got to be a better way! + +## """ + +## length = 1 +## if type(LineColors) == types.ListType: +## length = len(LineColors) +## else: +## LineColors = [LineColors] + +## if type(LineStyles) == types.ListType: +## length = len(LineStyles) +## else: +## LineStyles = [LineStyles] + +## if type(LineWidths) == types.ListType: +## length = len(LineWidths) +## else: +## LineWidths = [LineWidths] + +## if length > 1: +## if len(LineColors) == 1: +## LineColors = LineColors*length +## if len(LineStyles) == 1: +## LineStyles = LineStyles*length +## if len(LineWidths) == 1: +## LineWidths = LineWidths*length + +## self.Pens = [] +## for (LineColor,LineStyle,LineWidth) in zip(LineColors,LineStyles,LineWidths): +## if LineColor is None or LineStyle is None: +## self.Pens.append(wx.TRANSPARENT_PEN) +## # what's this for?> self.LineStyle = 'Transparent' +## if not self.PenList.has_key((LineColor,LineStyle,LineWidth)): +## Pen = wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle]) +## self.Pens.append(Pen) +## else: +## self.Pens.append(self.PenList[(LineColor,LineStyle,LineWidth)]) +## if length == 1: +## self.Pens = self.Pens[0] + + + +class DrawObject: + """ + This is the base class for all the objects that can be drawn. + + """ + + def __init__(self,InForeground = False): + self.InForeground = InForeground + + self._Canvas = None + + self.HitColor = None + self.CallBackFuncs = {} + + ## these are the defaults + self.HitAble = False + self.HitLine = True + self.HitFill = True + self.MinHitLineWidth = 3 + self.HitLineWidth = 3 ## this gets re-set by the subclasses if necessary + + # I pre-define all these as class variables to provide an easier + # interface, and perhaps speed things up by caching all the Pens + # and Brushes, although that may not help, as I think wx now + # does that on it's own. Send me a note if you know! + + BrushList = { + ( None,"Transparent") : wx.TRANSPARENT_BRUSH, + ("Blue","Solid") : wx.BLUE_BRUSH, + ("Green","Solid") : wx.GREEN_BRUSH, + ("White","Solid") : wx.WHITE_BRUSH, + ("Black","Solid") : wx.BLACK_BRUSH, + ("Grey","Solid") : wx.GREY_BRUSH, + ("MediumGrey","Solid") : wx.MEDIUM_GREY_BRUSH, + ("LightGrey","Solid") : wx.LIGHT_GREY_BRUSH, + ("Cyan","Solid") : wx.CYAN_BRUSH, + ("Red","Solid") : wx.RED_BRUSH + } + PenList = { + (None,"Transparent",1) : wx.TRANSPARENT_PEN, + ("Green","Solid",1) : wx.GREEN_PEN, + ("White","Solid",1) : wx.WHITE_PEN, + ("Black","Solid",1) : wx.BLACK_PEN, + ("Grey","Solid",1) : wx.GREY_PEN, + ("MediumGrey","Solid",1) : wx.MEDIUM_GREY_PEN, + ("LightGrey","Solid",1) : wx.LIGHT_GREY_PEN, + ("Cyan","Solid",1) : wx.CYAN_PEN, + ("Red","Solid",1) : wx.RED_PEN + } + + FillStyleList = { + "Transparent" : wx.TRANSPARENT, + "Solid" : wx.SOLID, + "BiDiagonalHatch": wx.BDIAGONAL_HATCH, + "CrossDiagHatch" : wx.CROSSDIAG_HATCH, + "FDiagonal_Hatch": wx.FDIAGONAL_HATCH, + "CrossHatch" : wx.CROSS_HATCH, + "HorizontalHatch": wx.HORIZONTAL_HATCH, + "VerticalHatch" : wx.VERTICAL_HATCH + } + + LineStyleList = { + "Solid" : wx.SOLID, + "Transparent": wx.TRANSPARENT, + "Dot" : wx.DOT, + "LongDash" : wx.LONG_DASH, + "ShortDash" : wx.SHORT_DASH, + "DotDash" : wx.DOT_DASH, + } + + def Bind(self, Event, CallBackFun): + self.CallBackFuncs[Event] = CallBackFun + self.HitAble = True + self._Canvas.UseHitTest = True + if not self._Canvas._HTdc: + self._Canvas.MakeNewHTdc() + if not self.HitColor: + if not self._Canvas.HitColorGenerator: + self._Canvas.HitColorGenerator = colorGenerator() + self._Canvas.HitColorGenerator.next() # first call to prevent the background color from being used. + self.HitColor = self._Canvas.HitColorGenerator.next() + self.SetHitPen(self.HitColor,self.HitLineWidth) + self.SetHitBrush(self.HitColor) + # put the object in the hit dict, indexed by it's color + if not self._Canvas.HitDict: + self._Canvas.MakeHitDict() + self._Canvas.HitDict[Event][self.HitColor] = (self) # put the object in the hit dict, indexed by it's color + + + def UnBindAll(self): + ## fixme: this only removes one from each list, there could be more. + if self._Canvas.HitDict: + for List in self._Canvas.HitDict.itervalues(): + try: + List.remove(self) + except ValueError: + pass + self.HitAble = False + + def SetBrush(self,FillColor,FillStyle): + if FillColor is None or FillStyle is None: + self.Brush = wx.TRANSPARENT_BRUSH + self.FillStyle = "Transparent" + else: + self.Brush = self.BrushList.setdefault( (FillColor,FillStyle), wx.Brush(FillColor,self.FillStyleList[FillStyle] ) ) + + def SetPen(self,LineColor,LineStyle,LineWidth): + if (LineColor is None) or (LineStyle is None): + self.Pen = wx.TRANSPARENT_PEN + self.LineStyle = 'Transparent' + else: + self.Pen = self.PenList.setdefault( (LineColor,LineStyle,LineWidth), wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle]) ) + + def SetHitBrush(self,HitColor): + if not self.HitFill: + self.HitBrush = wx.TRANSPARENT_BRUSH + else: + self.HitBrush = self.BrushList.setdefault( (HitColor,"solid"), wx.Brush(HitColor,self.FillStyleList["Solid"] ) ) + + def SetHitPen(self,HitColor,LineWidth): + if not self.HitLine: + self.HitPen = wx.TRANSPARENT_PEN + else: + self.HitPen = self.PenList.setdefault( (HitColor, "solid", LineWidth), wx.Pen(HitColor, LineWidth, self.LineStyleList["Solid"]) ) + + def PutInBackground(self): + if self._Canvas and self.InForeground: + self._Canvas._ForeDrawList.remove(self) + self._Canvas._DrawList.append(self) + self._Canvas._BackgroundDirty = True + self.InForeground = False + + def PutInForeground(self): + if self._Canvas and (not self.InForeground): + self._Canvas._ForeDrawList.append(self) + self._Canvas._DrawList.remove(self) + self._Canvas._BackgroundDirty = True + self.InForeground = True + +class XYObjectMixin: + """ + + This is a mixin class that provides some methods suitable for use + with objects that have a single (x,y) coordinate pair. + + """ + + def Move(self, Delta ): + """ + + Move(Delta): moves the object by delta, where delta is a + (dx,dy) pair. Ideally a Numpy array of shape (2,) + + """ + + Delta = asarray(Delta, Float) + self.XY += Delta + self.BoundingBox = self.BoundingBox + Delta + if self._Canvas: + self._Canvas.BoundingBoxDirty = True + +class PointsObjectMixin: + """ + + This is a mixin class that provides some methods suitable for use + with objects that have a set of (x,y) coordinate pairs. + + """ + +## This is code for the XYMixin object, it needs to be adapeted and tested. +## def Move(self, Delta ): +## """ + +## Move(Delta): moves the object by delta, where delta is an (dx, +## dy) pair. Ideally a Numpy array or shape (2,) + +## """ + +## Delta = array(Delta, Float) +## self.XY += Delta +## self.BoundingBox = self.BoundingBox + Delta##array((self.XY, (self.XY + self.WH)), Float) +## if self._Canvas: +## self._Canvas.BoundingBoxDirty = True + + def SetPoints(self,Points): + self.Points = Points + self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) + if self._Canvas: + self._Canvas.BoundingBoxDirty = True + + + +class Polygon(DrawObject,PointsObjectMixin): + + """ + + The Polygon class takes a list of 2-tuples, or a NX2 NumPy array of + point coordinates. so that Points[N][0] is the x-coordinate of + point N and Points[N][1] is the y-coordinate or Points[N,0] is the + x-coordinate of point N and Points[N,1] is the y-coordinate for + arrays. + + """ + def __init__(self, + Points, + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 1, + FillColor = None, + FillStyle = "Solid", + InForeground = False): + DrawObject.__init__(self,InForeground) + self.Points = array(Points,Float) # this DOES need to make a copy + self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) + + self.LineColor = LineColor + self.LineStyle = LineStyle + self.LineWidth = LineWidth + self.FillColor = FillColor + self.FillStyle = FillStyle + + self.HitLineWidth = max(LineWidth,self.MinHitLineWidth) + + self.SetPen(LineColor,LineStyle,LineWidth) + self.SetBrush(FillColor,FillStyle) + + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel = None, HTdc=None): + Points = WorldToPixel(self.Points) + dc.SetPen(self.Pen) + dc.SetBrush(self.Brush) + dc.DrawPolygon(Points) + if HTdc and self.HitAble: + HTdc.SetPen(self.HitPen) + HTdc.SetBrush(self.HitBrush) + HTdc.DrawPolygon(Points) + +##class PolygonSet(DrawObject): +## """ +## The PolygonSet class takes a Geometry.Polygon object. +## so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number! + +## it creates a set of line segments, from (x1,y1) to (x2,y2) + +## """ + +## def __init__(self,PolySet,LineColors,LineStyles,LineWidths,FillColors,FillStyles,InForeground = False): +## DrawObject.__init__(self, InForeground) + +## ##fixme: there should be some error checking for everything being the right length. + + +## self.Points = array(Points,Float) +## self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) + +## self.LineColors = LineColors +## self.LineStyles = LineStyles +## self.LineWidths = LineWidths +## self.FillColors = FillColors +## self.FillStyles = FillStyles + +## self.SetPens(LineColors,LineStyles,LineWidths) + +## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel): +## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): +## Points = WorldToPixel(self.Points) +## Points.shape = (-1,4) +## dc.DrawLineList(Points,self.Pens) + + +class Line(DrawObject,PointsObjectMixin): + """ + The Line class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates. + so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate + or Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays. + + It will draw a straight line if there are two points, and a polyline if there are more than two. + + """ + def __init__(self,Points, + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 1, + InForeground = False): + DrawObject.__init__(self, InForeground) + + + self.Points = array(Points,Float) + self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) + + self.LineColor = LineColor + self.LineStyle = LineStyle + self.LineWidth = LineWidth + + self.SetPen(LineColor,LineStyle,LineWidth) + + self.HitLineWidth = max(LineWidth,self.MinHitLineWidth) + + + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): + Points = WorldToPixel(self.Points) + dc.SetPen(self.Pen) + dc.DrawLines(Points) + if HTdc and self.HitAble: + HTdc.SetPen(self.HitPen) + HTdc.DrawLines(Points) + +##class LineSet(DrawObject, ObjectSetMixin): +## """ +## The LineSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates. +## so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number! + +## it creates a set of line segments, from (x1,y1) to (x2,y2) + +## """ + +## def __init__(self,Points,LineColors,LineStyles,LineWidths,InForeground = False): +## DrawObject.__init__(self, InForeground) + +## NumLines = len(Points) / 2 +## ##fixme: there should be some error checking for everything being the right length. + + +## self.Points = array(Points,Float) +## self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float) + +## self.LineColors = LineColors +## self.LineStyles = LineStyles +## self.LineWidths = LineWidths + +## self.SetPens(LineColors,LineStyles,LineWidths) + +## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel): +## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): +## Points = WorldToPixel(self.Points) +## Points.shape = (-1,4) +## dc.DrawLineList(Points,self.Pens) + +class PointSet(DrawObject): + """ + The PointSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates. + so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate + or Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays. + + Each point will be drawn the same color and Diameter. The Diameter is in screen points, + not world coordinates. + + At this point, the hit-test code does not distingish between the + points, you will only know that one of the poins got hit, not which + one. + + In the case of points, the HitLineWidth is used as diameter. + + """ + def __init__(self, Points, Color = "Black", Diameter = 1, InForeground = False): + DrawObject.__init__(self,InForeground) + + self.Points = array(Points,Float) + self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point + self.BoundingBox = array(((min(self.Points[:,0]), + min(self.Points[:,1])), + (max(self.Points[:,0]), + max(self.Points[:,1]))),Float) + + self.Color = Color + self.Diameter = Diameter + + self.HitLineWidth = self.MinHitLineWidth + self.SetPen(Color,"Solid",1) + self.SetBrush(Color,"Solid") + + def SetPoints(self,Points): + self.Points = array(Points, Float) + self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point + self.BoundingBox = array(((min(self.Points[:,0]), + min(self.Points[:,1]) ), + (max(self.Points[:,0]), + max(self.Points[:,1]) ) ) ) + if self._Canvas: + self._Canvas.BoundingBoxDirty = True + + def DrawD2(self, dc, Points): + # A Little optimization for a diameter2 - point + dc.DrawPointList(Points) + dc.DrawPointList(Points + (1,0)) + dc.DrawPointList(Points + (0,1)) + dc.DrawPointList(Points + (1,1)) + + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): + dc.SetPen(self.Pen) + Points = WorldToPixel(self.Points) + if self.Diameter <= 1: + dc.DrawPointList(Points) + elif self.Diameter <= 2: + self.DrawD2(dc, Points) + else: + dc.SetBrush(self.Brush) + radius = int(round(self.Diameter/2)) + for xy in Points: + dc.DrawEllipsePointSize( (xy - radius), (self.Diameter, self.Diameter) ) + if HTdc and self.HitAble: + HTdc.SetPen(self.HitPen) + if self.Diameter <= 1: + HTdc.DrawPointList(Points) + elif self.Diameter <= 2: + self.DrawD2(HTdc, Points) + else: + HTdc.SetBrush(self.HitBrush) + radius = int(round(self.Diameter/2)) + for xy in Points: + HTdc.DrawEllipsePointSize( (xy - radius), (self.Diameter, self.Diameter) ) + +#### Does anyone need this? +##class Dot(DrawObject): +## """ +## The Dot class takes an x.y coordinate pair, and the Diameter of the circle. +## The Diameter is in pixels, so it won't change with zoom. + +## Also Fill and line data + +## """ +## def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,InForeground = False): +## DrawObject.__init__(self,InForeground) + +## self.X = x +## self.Y = y +## self.Diameter = Diameter +## # NOTE: the bounding box does not include the diameter of the dot, as that is in pixel coords. +## # If this is a problem, perhaps you should use a circle, instead! +## self.BoundingBox = array(((x,y),(x,y)),Float) + +## self.LineColor = LineColor +## self.LineStyle = LineStyle +## self.LineWidth = LineWidth +## self.FillColor = FillColor +## self.FillStyle = FillStyle + +## self.SetPen(LineColor,LineStyle,LineWidth) +## self.SetBrush(FillColor,FillStyle) + +## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): +## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel): +## dc.SetPen(self.Pen) +## dc.SetBrush(self.Brush) +## radius = int(round(self.Diameter/2)) +## (X,Y) = WorldToPixel((self.X,self.Y)) +## dc.DrawEllipse((X - radius), (Y - radius), self.Diameter, self.Diameter) + +class RectEllipse(DrawObject, XYObjectMixin): + def __init__(self,x,y,width,height, + LineColor = "Black", + LineStyle = "Solid", + LineWidth = 1, + FillColor = None, + FillStyle = "Solid", + InForeground = False): + + DrawObject.__init__(self,InForeground) + + self.XY = array( (x, y), Float) + self.WH = array( (width, height), Float ) + self.BoundingBox = array(((x,y), (self.XY + self.WH)), Float) + self.LineColor = LineColor + self.LineStyle = LineStyle + self.LineWidth = LineWidth + self.FillColor = FillColor + self.FillStyle = FillStyle + + self.HitLineWidth = max(LineWidth,self.MinHitLineWidth) + + self.SetPen(LineColor,LineStyle,LineWidth) + self.SetBrush(FillColor,FillStyle) + + + def SetUpDraw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc): + dc.SetPen(self.Pen) + dc.SetBrush(self.Brush) + if HTdc and self.HitAble: + HTdc.SetPen(self.HitPen) + HTdc.SetBrush(self.HitBrush) + return ( WorldToPixel(self.XY), + ScaleWorldToPixel(self.WH) ) + + def SetXY(self, x, y): + self.XY = array( (x, y), Float) + self.BoundingBox = array((self.XY, (self.XY + self.WH) ), Float) + if self._Canvas: + self._Canvas.BoundingBoxDirty = True + + +class Rectangle(RectEllipse): +# def __init__(*args, **kwargs): +# RectEllipse.__init__(*args, **kwargs) +# raise "an error" + + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): + ( XY, WH ) = self.SetUpDraw(dc, + WorldToPixel, + ScaleWorldToPixel, + HTdc) + dc.DrawRectanglePointSize(XY, WH) + if HTdc and self.HitAble: + HTdc.DrawRectanglePointSize(XY, WH) + +class Ellipse(RectEllipse): +# def __init__(*args, **kwargs): +# RectEllipse.__init__(*args, **kwargs) + + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): + ( XY, WH ) = self.SetUpDraw(dc, + WorldToPixel, + ScaleWorldToPixel, + HTdc) + dc.DrawEllipsePointSize(XY, WH) + if HTdc and self.HitAble: + HTdc.DrawEllipsePointSize(XY, WH) + +class Circle(Ellipse): + def __init__(self, x ,y, Diameter, **kwargs): + RectEllipse.__init__(self , + x-Diameter/2., + y-Diameter/2., + Diameter, + Diameter, + **kwargs) + +class TextObjectMixin: + """ + + A mix in class that holds attributes and methods that are needed by + the Text objects + + """ + + ## I'm caching fonts, because on GTK, getting a new font can take a + ## while. However, it gets cleared after every full draw as hanging + ## on to a bunch of large fonts takes a massive amount of memory. + + FontList = {} + + def SetFont(self, Size, Family, Style, Weight, Underline, FaceName): + self.Font = self.FontList.setdefault( (Size, + Family, + Style, + Weight, + Underline, + FaceName), + wx.Font(Size, + Family, + Style, + Weight, + Underline, + FaceName) ) + return self.Font + + ## store the function that shift the coords for drawing text. The + ## "c" parameter is the correction for world coordinates, rather + ## than pixel coords as the y axis is reversed + ShiftFunDict = {'tl': lambda x, y, w, h, world=0: (x, y) , + 'tc': lambda x, y, w, h, world=0: (x - w/2, y) , + 'tr': lambda x, y, w, h, world=0: (x - w, y) , + 'cl': lambda x, y, w, h, world=0: (x, y - h/2 + world*h) , + 'cc': lambda x, y, w, h, world=0: (x - w/2, y - h/2 + world*h) , + 'cr': lambda x, y, w, h, world=0: (x - w, y - h/2 + world*h) , + 'bl': lambda x, y, w, h, world=0: (x, y - h + 2*world*h) , + 'bc': lambda x, y, w, h, world=0: (x - w/2, y - h + 2*world*h) , + 'br': lambda x, y, w, h, world=0: (x - w, y - h + 2*world*h)} + +class Text(DrawObject, TextObjectMixin): + """ + This class creates a text object, placed at the coordinates, + x,y. the "Position" argument is a two charactor string, indicating + where in relation to the coordinates the string should be oriented. + + The first letter is: t, c, or b, for top, center and bottom The + second letter is: l, c, or r, for left, center and right The + position refers to the position relative to the text itself. It + defaults to "tl" (top left). + + Size is the size of the font in pixels, or in points for printing + (if it ever gets implimented). Those will be the same, If you assume + 72 PPI. + + Family: + Font family, a generic way of referring to fonts without + specifying actual facename. One of: + wx.DEFAULT: Chooses a default font. + wx.DECORATIVE: A decorative font. + wx.ROMAN: A formal, serif font. + wx.SCRIPT: A handwriting font. + wx.SWISS: A sans-serif font. + wx.MODERN: A fixed pitch font. + NOTE: these are only as good as the wxWindows defaults, which aren't so good. + Style: + One of wx.NORMAL, wx.SLANT and wx.ITALIC. + Weight: + One of wx.NORMAL, wx.LIGHT and wx.BOLD. + Underline: + The value can be True or False. At present this may have an an + effect on Windows only. + + Alternatively, you can set the kw arg: Font, to a wx.Font, and the above will be ignored. + + The size is fixed, and does not scale with the drawing. + + The hit-test is done on the entire text extent + + """ + + def __init__(self,String,x,y, + Size = 12, + Color = "Black", + BackgroundColor = None, + Family = wx.MODERN, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underline = False, + Position = 'tl', + InForeground = False, + Font = None): + + DrawObject.__init__(self,InForeground) + + self.String = String + # Input size in in Pixels, compute points size from PPI info. + # fixme: for printing, we'll have to do something a little different + self.Size = int(round(72.0 * Size / ScreenPPI)) + + self.Color = Color + self.BackgroundColor = BackgroundColor + + if not Font: + FaceName = '' + else: + FaceName = Font.GetFaceName() + Family = Font.GetFamily() + Size = Font.GetPointSize() + Style = Font.GetStyle() + Underlined = Font.GetUnderlined() + Weight = Font.GetWeight() + self.SetFont(Size, Family, Style, Weight, Underline, FaceName) + + self.BoundingBox = array(((x,y),(x,y)),Float) + + self.XY = ( x,y ) + + # use a memDC -- ScreenDC doesn't work with 2.5.1 and GTK2 + #dc = wx.MemoryDC() + #bitmap = wx.EmptyBitmap(1, 1) + #dc.SelectObject(bitmap) + #dc.SetFont(self.Font) + #(self.TextWidth, self.TextHeight) = dc.GetTextExtent(self.String) + (self.TextWidth, self.TextHeight) = (None, None) + self.ShiftFun = self.ShiftFunDict[Position] + + def SetXY(self, x, y): + self.XY = ( x,y ) + self.BoundingBox = array((self.XY, self.XY),Float) + if self._Canvas: + self._Canvas.BoundingBoxDirty = True + + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): + XY = WorldToPixel(self.XY) + dc.SetFont(self.Font) + dc.SetTextForeground(self.Color) + if self.BackgroundColor: + dc.SetBackgroundMode(wx.SOLID) + dc.SetTextBackground(self.BackgroundColor) + else: + dc.SetBackgroundMode(wx.TRANSPARENT) + if self.TextWidth is None or self.TextHeight is None: + (self.TextWidth, self.TextHeight) = dc.GetTextExtent(self.String) + XY = self.ShiftFun(XY[0], XY[1], self.TextWidth, self.TextHeight) + dc.DrawTextPoint(self.String, XY) + if HTdc and self.HitAble: + HTdc.SetPen(self.HitPen) + HTdc.SetBrush(self.HitBrush) + HTdc.DrawRectanglePointSize(XY, (self.TextWidth, self.TextHeight) ) + +class ScaledText(DrawObject, TextObjectMixin, XYObjectMixin): + """ + This class creates a text object that is scaled when zoomed. It is + placed at the coordinates, x,y. the "Position" argument is a two + charactor string, indicating where in relation to the coordinates + the string should be oriented. + + The first letter is: t, c, or b, for top, center and bottom The + second letter is: l, c, or r, for left, center and right The + position refers to the position relative to the text itself. It + defaults to "tl" (top left). + + Size is the size of the font in world coordinates. + + Family: + Font family, a generic way of referring to fonts without + specifying actual facename. One of: + wx.DEFAULT: Chooses a default font. + wx.DECORATI: A decorative font. + wx.ROMAN: A formal, serif font. + wx.SCRIPT: A handwriting font. + wx.SWISS: A sans-serif font. + wx.MODERN: A fixed pitch font. + NOTE: these are only as good as the wxWindows defaults, which aren't so good. + Style: + One of wx.NORMAL, wx.SLANT and wx.ITALIC. + Weight: + One of wx.NORMAL, wx.LIGHT and wx.BOLD. + Underline: + The value can be True or False. At present this may have an an + effect on Windows only. + + Alternatively, you can set the kw arg: Font, to a wx.Font, and the + above will be ignored. The size of the font you specify will be + ignored, but the rest of it's attributes will be preserved. + + The size will scale as the drawing is zoomed. + + Bugs/Limitations: + + As fonts are scaled, the do end up a little different, so you don't + get exactly the same picture as you scale up and doen, but it's + pretty darn close. + + On wxGTK1 on my Linux system, at least, using a font of over about + 3000 pts. brings the system to a halt. It's the Font Server using + huge amounts of memory. My work around is to max the font size to + 3000 points, so it won't scale past there. GTK2 uses smarter font + drawing, so that may not be an issue in future versions, so feel + free to test. Another smarter way to do it would be to set a global + zoom limit at that point. + + The hit-test is done on the entire text extent. This could be made + optional, but I havn't gotten around to it. + + """ + + def __init__(self, String, x, y , Size, + Color = "Black", + BackgroundColor = None, + Family = wx.MODERN, + Style = wx.NORMAL, + Weight = wx.NORMAL, + Underline = False, + Position = 'tl', + Font = None, + InForeground = False): + + DrawObject.__init__(self,InForeground) + + self.String = String + self.XY = array( (x, y), Float) + self.Size = Size + self.Color = Color + self.BackgroundColor = BackgroundColor + self.Family = Family + self.Style = Style + self.Weight = Weight + self.Underline = Underline + if not Font: + self.FaceName = '' + else: + self.FaceName = Font.GetFaceName() + self.Family = Font.GetFamily() + self.Style = Font.GetStyle() + self.Underlined = Font.GetUnderlined() + self.Weight = Font.GetWeight() + + # Experimental max font size value on wxGTK2: this works OK on + # my system If it's any larger, there is a crash, with the + # message: The application 'FloatCanvasDemo.py' lost its + # connection to the display :0.0; most likely the X server was + # shut down or you killed/destroyed the application. + self.MaxSize = 2750 + + self.ShiftFun = self.ShiftFunDict[Position] + + ## Compute the BB + ## this isn't exact, as fonts don't scale exactly. + dc = wx.MemoryDC() + bitmap = wx.EmptyBitmap(1, 1) + dc.SelectObject(bitmap) #wxMac needs a Bitmap selected for GetTextExtent to work. + DrawingSize = 40 # pts This effectively determines the resolution that the BB is computed to. + ScaleFactor = float(Size) / DrawingSize + dc.SetFont(self.SetFont(DrawingSize, self.Family, self.Style, self.Weight, self.Underline, self.FaceName) ) + (w,h) = dc.GetTextExtent(self.String) + w = w * ScaleFactor + h = h * ScaleFactor + x, y = self.ShiftFun(x, y, w, h, world = 1) + self.BoundingBox = array(((x, y-h ),(x + w, y)),Float) + + # the new coords are set to the corner of the BB: + #self.X = self.BoundingBox[0,0] + #self.Y = self.BoundingBox[1,1] + def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): + (X,Y) = WorldToPixel( (self.XY) ) + + # compute the font size: + Size = abs( ScaleWorldToPixel( (self.Size, self.Size) )[1] ) # only need a y coordinate length + ## Check to see if the font size is large enough to blow up the X font server + ## If so, limit it. Would it be better just to not draw it? + ## note that this limit is dependent on how much memory you have, etc. + if Size > self.MaxSize: + Size = self.MaxSize + dc.SetFont(self.SetFont(Size, self.Family, self.Style, self.Weight, self.Underline, self.FaceName)) + dc.SetTextForeground(self.Color) + if self.BackgroundColor: + dc.SetBackgroundMode(wx.SOLID) + dc.SetTextBackground(self.BackgroundColor) + else: + dc.SetBackgroundMode(wx.TRANSPARENT) + (w,h) = dc.GetTextExtent(self.String) + # compute the shift, and adjust the coordinates, if neccesary + # This had to be put in here, because it changes with Zoom, as + # fonts don't scale exactly. + xy = self.ShiftFun(X, Y, w, h) + + dc.DrawTextPoint(self.String, xy) + if HTdc and self.HitAble: + HTdc.SetPen(self.HitPen) + HTdc.SetBrush(self.HitBrush) + HTdc.DrawRectanglePointSize(xy, (w, h) ) + + +#--------------------------------------------------------------------------- +class FloatCanvas(wx.Panel): + """ + FloatCanvas.py + + This is a high level window for drawing maps and anything else in an + arbitrary coordinate system. + + The goal is to provide a convenient way to draw stuff on the screen + without having to deal with handling OnPaint events, converting to pixel + coordinates, knowing about wxWindows brushes, pens, and colors, etc. It + also provides virtually unlimited zooming and scrolling + + I am using it for two things: + 1) general purpose drawing in floating point coordinates + 2) displaying map data in Lat-long coordinates + + If the projection is set to None, it will draw in general purpose + floating point coordinates. If the projection is set to 'FlatEarth', it + will draw a FlatEarth projection, centered on the part of the map that + you are viewing. You can also pass in your own projection function. + + It is double buffered, so re-draws after the window is uncovered by something + else are very quick. + + It relies on NumPy, which is needed for speed (maybe, I havn't profiled it) + + Bugs and Limitations: + Lots: patches, fixes welcome + + For Map drawing: It ignores the fact that the world is, in fact, a + sphere, so it will do strange things if you are looking at stuff near + the poles or the date line. so far I don't have a need to do that, so I + havn't bothered to add any checks for that yet. + + Zooming: + I have set no zoom limits. What this means is that if you zoom in really + far, you can get integer overflows, and get wierd results. It + doesn't seem to actually cause any problems other than wierd output, at + least when I have run it. + + Speed: + I have done a couple of things to improve speed in this app. The one + thing I have done is used NumPy Arrays to store the coordinates of the + points of the objects. This allowed me to use array oriented functions + when doing transformations, and should provide some speed improvement + for objects with a lot of points (big polygons, polylines, pointsets). + + The real slowdown comes when you have to draw a lot of objects, because + you have to call the wx.DC.DrawSomething call each time. This is plenty + fast for tens of objects, OK for hundreds of objects, but pretty darn + slow for thousands of objects. + + The solution is to be able to pass some sort of object set to the DC + directly. I've used DC.DrawPointList(Points), and it helped a lot with + drawing lots of points. I havn't got a LineSet type object, so I havn't + used DC.DrawLineList yet. I'd like to get a full set of DrawStuffList() + methods implimented, and then I'd also have a full set of Object sets + that could take advantage of them. I hope to get to it some day. + + Mouse Events: + + At this point, there are a full set of custom mouse events. They are + just like the rebulsr mouse events, but include an extra attribute: + Event.GetCoords(), that returns the (x,y) position in world + coordinates, as a length-2 NumPy vector of Floats. + + Copyright: Christopher Barker + + License: Same as the version of wxPython you are using it with + + Please let me know if you're using this!!! + + Contact me at: + + Chris.Barker@noaa.gov + + """ + + def __init__(self, parent, id = -1, + size = wx.DefaultSize, + ProjectionFun = None, + BackgroundColor = "WHITE", + Debug = False): + + wx.Panel.__init__( self, parent, id, wx.DefaultPosition, size) + + global ScreenPPI ## A global variable to hold the Pixels per inch that wxWindows thinks is in use. + dc = wx.ScreenDC() + ScreenPPI = dc.GetPPI()[0] # Assume square pixels + del dc + + self.HitColorGenerator = None + self.UseHitTest = None + + self.NumBetweenBlits = 500 + + self.BackgroundBrush = wx.Brush(BackgroundColor,wx.SOLID) + + self.Debug = Debug + + wx.EVT_PAINT(self, self.OnPaint) + wx.EVT_SIZE(self, self.OnSize) + + wx.EVT_LEFT_DOWN(self, self.LeftDownEvent ) + wx.EVT_LEFT_UP(self, self.LeftUpEvent ) + wx.EVT_LEFT_DCLICK(self, self.LeftDoubleClickEvent ) + wx.EVT_MIDDLE_DOWN(self, self.MiddleDownEvent ) + wx.EVT_MIDDLE_UP(self, self.MiddleUpEvent ) + wx.EVT_MIDDLE_DCLICK(self, self.MiddleDoubleClickEvent ) + wx.EVT_RIGHT_DOWN(self, self.RightDownEvent) + wx.EVT_RIGHT_UP(self, self.RightUpEvent ) + wx.EVT_RIGHT_DCLICK(self, self.RightDoubleCLickEvent ) + wx.EVT_MOTION(self, self.MotionEvent ) + wx.EVT_MOUSEWHEEL(self, self.WheelEvent ) + + ## CHB: I'm leaving these out for now. + #wx.EVT_ENTER_WINDOW(self, self. ) + #wx.EVT_LEAVE_WINDOW(self, self. ) + + ## create the Hit Test Dicts: + self.HitDict = None + + + self._DrawList = [] + self._ForeDrawList = [] + self._ForegroundBuffer = None + self.BoundingBox = None + self.BoundingBoxDirty = False + self.ViewPortCenter= array( (0,0), Float) + + self.SetProjectionFun(ProjectionFun) + + self.MapProjectionVector = array( (1,1), Float) # No Projection to start! + self.TransformVector = array( (1,-1), Float) # default Transformation + + self.Scale = 1 + + self.GUIMode = None + self.StartRBBox = None + self.PrevRBBox = None + self.StartMove = None + self.PrevMoveXY = None + self.ObjectUnderMouse = None + + # called just to make sure everything is initialized + self.OnSize(None) + + self.InHereNum = 0 + + def SetProjectionFun(self,ProjectionFun): + if ProjectionFun == 'FlatEarth': + self.ProjectionFun = self.FlatEarthProjection + elif type(ProjectionFun) == types.FunctionType: + self.ProjectionFun = ProjectionFun + elif ProjectionFun is None: + self.ProjectionFun = lambda x=None: array( (1,1), Float) + else: + raise FloatCanvasException('Projectionfun must be either: "FlatEarth", None, or a function that takes the ViewPortCenter and returns a MapProjectionVector') + + def FlatEarthProjection(self,CenterPoint): + return array((cos(pi*CenterPoint[1]/180),1),Float) + + def SetMode(self,Mode): + if Mode in ["ZoomIn","ZoomOut","Move","Mouse",None]: + self.GUIMode = Mode + else: + raise FloatCanvasException('"%s" is Not a valid Mode'%Mode) + + def MakeHitDict(self): + ##fixme: Should this just be None if nothing has been bound? + self.HitDict = {EVT_FC_LEFT_DOWN: {}, + EVT_FC_LEFT_UP: {}, + EVT_FC_LEFT_DCLICK: {}, + EVT_FC_MIDDLE_DOWN: {}, + EVT_FC_MIDDLE_UP: {}, + EVT_FC_MIDDLE_DCLICK: {}, + EVT_FC_RIGHT_DOWN: {}, + EVT_FC_RIGHT_UP: {}, + EVT_FC_RIGHT_DCLICK: {}, + EVT_FC_ENTER_OBJECT: {}, + EVT_FC_LEAVE_OBJECT: {}, + } + + def RaiseMouseEvent(self, Event, EventType): + """ + This is called in various other places to raise a Mouse Event + """ + #print "in Raise Mouse Event", Event + pt = self.PixelToWorld( Event.GetPosition() ) + evt = MouseEvent(EventType, Event, self.GetId(), pt) + self.GetEventHandler().ProcessEvent(evt) + + def HitTest(self, event, HitEvent): + if self.HitDict: + # check if there are any objects in the dict for this event + if self.HitDict[ HitEvent ]: + xy = event.GetPosition() + if self._ForegroundHTdc: + hitcolor = self._ForegroundHTdc.GetPixelPoint( xy ) + else: + hitcolor = self._HTdc.GetPixelPoint( xy ) + color = ( hitcolor.Red(), hitcolor.Green(), hitcolor.Blue() ) + if color in self.HitDict[ HitEvent ]: + Object = self.HitDict[ HitEvent ][color] + ## Add the hit coords to the Object + Object.HitCoords = self.PixelToWorld( xy ) + Object.CallBackFuncs[HitEvent](Object) + return True + return False + + def MouseOverTest(self, event): + ##fixme: Can this be cleaned up? + if self.HitDict: + xy = event.GetPosition() + if self._ForegroundHTdc: + hitcolor = self._ForegroundHTdc.GetPixelPoint( xy ) + else: + hitcolor = self._HTdc.GetPixelPoint( xy ) + color = ( hitcolor.Red(), hitcolor.Green(), hitcolor.Blue() ) + OldObject = self.ObjectUnderMouse + ObjectCallbackCalled = False + if color in self.HitDict[ EVT_FC_ENTER_OBJECT ]: + Object = self.HitDict[ EVT_FC_ENTER_OBJECT][color] + if (OldObject is None): + try: + Object.CallBackFuncs[EVT_FC_ENTER_OBJECT](Object) + ObjectCallbackCalled = True + except KeyError: + pass # this means the enter event isn't bound for that object + elif OldObject == Object: # the mouse is still on the same object + pass + ## Is the mouse on a differnt object as it was... + elif not (Object == OldObject): + # call the leave object callback + try: + OldObject.CallBackFuncs[EVT_FC_LEAVE_OBJECT](OldObject) + ObjectCallbackCalled = True + except KeyError: + pass # this means the leave event isn't bound for that object + try: + Object.CallBackFuncs[EVT_FC_ENTER_OBJECT](Object) + ObjectCallbackCalled = True + except KeyError: + pass # this means the enter event isn't bound for that object + ## set the new object under mouse + self.ObjectUnderMouse = Object + elif color in self.HitDict[ EVT_FC_LEAVE_OBJECT ]: + Object = self.HitDict[ EVT_FC_LEAVE_OBJECT][color] + self.ObjectUnderMouse = Object + else: + # no objects under mouse bound to mouse-over events + self.ObjectUnderMouse = None + if OldObject: + try: + OldObject.CallBackFuncs[EVT_FC_LEAVE_OBJECT](OldObject) + ObjectCallbackCalled = True + except KeyError: + pass # this means the leave event isn't bound for that object + return ObjectCallbackCalled + + + ## fixme: There is a lot of repeated code here + ## Is there a better way? + def LeftDoubleClickEvent(self,event): + if self.GUIMode == "Mouse": + EventType = EVT_FC_LEFT_DCLICK + if not self.HitTest(event, EventType): + self.RaiseMouseEvent(event, EventType) + + + def MiddleDownEvent(self,event): + if self.GUIMode == "Mouse": + EventType = EVT_FC_MIDDLE_DOWN + if not self.HitTest(event, EventType): + self.RaiseMouseEvent(event, EventType) + + def MiddleUpEvent(self,event): + if self.GUIMode == "Mouse": + EventType = EVT_FC_MIDDLE_UP + if not self.HitTest(event, EventType): + self.RaiseMouseEvent(event, EventType) + + def MiddleDoubleClickEvent(self,event): + if self.GUIMode == "Mouse": + EventType = EVT_FC_MIDDLE_DCLICK + if not self.HitTest(event, EventType): + self.RaiseMouseEvent(event, EventType) + + def RightUpEvent(self,event): + if self.GUIMode == "Mouse": + EventType = EVT_FC_RIGHT_UP + if not self.HitTest(event, EventType): + self.RaiseMouseEvent(event, EventType) + + def RightDoubleCLickEvent(self,event): + if self.GUIMode == "Mouse": + EventType = EVT_FC_RIGHT_DCLICK + if not self.HitTest(event, EventType): + self.RaiseMouseEvent(event, EventType) + + def WheelEvent(self,event): + if self.GUIMode == "Mouse": + self.RaiseMouseEvent(event, EVT_FC_MOUSEWHEEL) + + + def LeftDownEvent(self,event): + if self.GUIMode: + if self.GUIMode == "ZoomIn": + self.StartRBBox = array( event.GetPosition() ) + self.PrevRBBox = None + self.CaptureMouse() + elif self.GUIMode == "ZoomOut": + Center = self.PixelToWorld( event.GetPosition() ) + self.Zoom(1/1.5,Center) + elif self.GUIMode == "Move": + self.StartMove = array( event.GetPosition() ) + self.PrevMoveXY = (0,0) + elif self.GUIMode == "Mouse": + ## check for a hit + if not self.HitTest(event, EVT_FC_LEFT_DOWN): + self.RaiseMouseEvent(event,EVT_FC_LEFT_DOWN) + else: + pass + + def LeftUpEvent(self,event): + if self.HasCapture(): + self.ReleaseMouse() + if self.GUIMode: + if self.GUIMode == "ZoomIn": + if event.LeftUp() and not self.StartRBBox is None: + self.PrevRBBox = None + EndRBBox = event.GetPosition() + StartRBBox = self.StartRBBox + # if mouse has moved less that ten pixels, don't use the box. + if ( abs(StartRBBox[0] - EndRBBox[0]) > 10 + and abs(StartRBBox[1] - EndRBBox[1]) > 10 ): + EndRBBox = self.PixelToWorld(EndRBBox) + StartRBBox = self.PixelToWorld(StartRBBox) + BB = array(((min(EndRBBox[0],StartRBBox[0]), + min(EndRBBox[1],StartRBBox[1])), + (max(EndRBBox[0],StartRBBox[0]), + max(EndRBBox[1],StartRBBox[1]))),Float) + self.ZoomToBB(BB) + else: + Center = self.PixelToWorld(StartRBBox) + self.Zoom(1.5,Center) + self.StartRBBox = None + elif self.GUIMode == "Move": + if not self.StartMove is None: + StartMove = self.StartMove + EndMove = array((event.GetX(),event.GetY())) + if sum((StartMove-EndMove)**2) > 16: + self.Move(StartMove-EndMove,'Pixel') + self.StartMove = None + elif self.GUIMode == "Mouse": + EventType = EVT_FC_LEFT_UP + if not self.HitTest(event, EventType): + self.RaiseMouseEvent(event, EventType) + else: + pass + + def MotionEvent(self,event): + if self.GUIMode: + if self.GUIMode == "ZoomIn": + if event.Dragging() and event.LeftIsDown() and not (self.StartRBBox is None): + xy0 = self.StartRBBox + xy1 = array( event.GetPosition() ) + wh = abs(xy1 - xy0) + wh[0] = max(wh[0], int(wh[1]*self.AspectRatio)) + wh[1] = int(wh[0] / self.AspectRatio) + xy_c = (xy0 + xy1) / 2 + dc = wx.ClientDC(self) + dc.BeginDrawing() + dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) + dc.SetBrush(wx.TRANSPARENT_BRUSH) + dc.SetLogicalFunction(wx.XOR) + if self.PrevRBBox: + dc.DrawRectanglePointSize(*self.PrevRBBox) + self.PrevRBBox = ( xy_c - wh/2, wh ) + dc.DrawRectanglePointSize( *self.PrevRBBox ) + dc.EndDrawing() + elif self.GUIMode == "Move": + if event.Dragging() and event.LeftIsDown() and not self.StartMove is None: + xy1 = array( event.GetPosition() ) + wh = self.PanelSize + xy_tl = xy1 - self.StartMove + dc = wx.ClientDC(self) + dc.BeginDrawing() + x1,y1 = self.PrevMoveXY + x2,y2 = xy_tl + w,h = self.PanelSize + if x2 > x1 and y2 > y1: + xa = xb = x1 + ya = yb = y1 + wa = w + ha = y2 - y1 + wb = x2- x1 + hb = h + elif x2 > x1 and y2 <= y1: + xa = x1 + ya = y1 + wa = x2 - x1 + ha = h + xb = x1 + yb = y2 + h + wb = w + hb = y1 - y2 + elif x2 <= x1 and y2 > y1: + xa = x1 + ya = y1 + wa = w + ha = y2 - y1 + xb = x2 + w + yb = y1 + wb = x1 - x2 + hb = h - y2 + y1 + elif x2 <= x1 and y2 <= y1: + xa = x2 + w + ya = y1 + wa = x1 - x2 + ha = h + xb = x1 + yb = y2 + h + wb = w + hb = y1 - y2 + + dc.SetPen(wx.TRANSPARENT_PEN) + dc.SetBrush(self.BackgroundBrush) + dc.DrawRectangle(xa, ya, wa, ha) + dc.DrawRectangle(xb, yb, wb, hb) + self.PrevMoveXY = xy_tl + if self._ForegroundBuffer: + dc.DrawBitmapPoint(self._ForegroundBuffer,xy_tl) + else: + dc.DrawBitmapPoint(self._Buffer,xy_tl) + dc.EndDrawing() + elif self.GUIMode == "Mouse": + ## Only do something if there are mouse over events bound + if self.HitDict and (self.HitDict[ EVT_FC_ENTER_OBJECT ] or self.HitDict[ EVT_FC_LEAVE_OBJECT ] ): + if not self.MouseOverTest(event): + self.RaiseMouseEvent(event,EVT_FC_MOTION) + else: + pass + self.RaiseMouseEvent(event,EVT_FC_MOTION) + else: + pass + + def RightDownEvent(self,event): + if self.GUIMode: + if self.GUIMode == "ZoomIn": + Center = self.PixelToWorld((event.GetX(),event.GetY())) + self.Zoom(1/1.5,Center) + elif self.GUIMode == "ZoomOut": + Center = self.PixelToWorld((event.GetX(),event.GetY())) + self.Zoom(1.5,Center) + elif self.GUIMode == "Mouse": + EventType = EVT_FC_RIGHT_DOWN + if not self.HitTest(event, EventType): + self.RaiseMouseEvent(event, EventType) + else: + pass + + def MakeNewBuffers(self): + self._BackgroundDirty = True + # Make new offscreen bitmap: + self._Buffer = wx.EmptyBitmap(*self.PanelSize) + #dc = wx.MemoryDC() + #dc.SelectObject(self._Buffer) + #dc.Clear() + if self._ForeDrawList: + self._ForegroundBuffer = wx.EmptyBitmap(*self.PanelSize) + else: + self._ForegroundBuffer = None + if self.UseHitTest: + self.MakeNewHTdc() + else: + self._HTdc = None + self._ForegroundHTdc = None + + def MakeNewHTdc(self): + ## Note: While it's considered a "bad idea" to keep a + ## MemoryDC around I'm doing it here because a wx.Bitmap + ## doesn't have a GetPixel method so a DC is needed to do + ## the hit-test. It didn't seem like a good idea to re-create + ## a wx.MemoryDC on every single mouse event, so I keep it + ## around instead + self._HTdc = wx.MemoryDC() + self._HTBitmap = wx.EmptyBitmap(*self.PanelSize) + self._HTdc.SelectObject( self._HTBitmap ) + self._HTdc.SetBackground(wx.BLACK_BRUSH) + if self._ForeDrawList: + self._ForegroundHTdc = wx.MemoryDC() + self._ForegroundHTBitmap = wx.EmptyBitmap(*self.PanelSize) + self._ForegroundHTdc.SelectObject( self._ForegroundHTBitmap ) + self._ForegroundHTdc.SetBackground(wx.BLACK_BRUSH) + else: + self._ForegroundHTdc = None + + def OnSize(self,event): + self.PanelSize = array(self.GetClientSizeTuple(),Int32) + self.HalfPanelSize = self.PanelSize / 2 # lrk: added for speed in WorldToPixel + if self.PanelSize[0] == 0 or self.PanelSize[1] == 0: + self.AspectRatio = 1.0 + else: + self.AspectRatio = float(self.PanelSize[0]) / self.PanelSize[1] + self.MakeNewBuffers() + self.Draw() + + def OnPaint(self, event): + dc = wx.PaintDC(self) + if self._ForegroundBuffer: + dc.DrawBitmap(self._ForegroundBuffer,0,0) + else: + dc.DrawBitmap(self._Buffer,0,0) + + def Draw(self, Force=False): + """ + There is a main buffer set up to double buffer the screen, so + you can get quick re-draws when the window gets uncovered. + + If there are any objects in self._ForeDrawList, then the + background gets drawn to a new buffer, and the foreground + objects get drawn on top of it. The final result if blitted to + the screen, and stored for future Paint events. This is done so + that you can have a complicated background, but have something + changing on the foreground, without having to wait for the + background to get re-drawn. This can be used to support simple + animation, for instance. + + """ + #print "In Draw" + if self.Debug: start = clock() + ScreenDC = wx.ClientDC(self) + ViewPortWorld = ( self.PixelToWorld((0,0)), + self.PixelToWorld(self.PanelSize) ) + ViewPortBB = array( ( minimum.reduce(ViewPortWorld), + maximum.reduce(ViewPortWorld) ) ) + dc = wx.MemoryDC() + dc.SelectObject(self._Buffer) + if self._BackgroundDirty or Force: + #print "Background is Dirty" + dc.SetBackground(self.BackgroundBrush) + dc.Clear() + if self._HTdc: + self._HTdc.Clear() + self._DrawObjects(dc, self._DrawList, ScreenDC, ViewPortBB, self._HTdc) + self._BackgroundDirty = False + + if self._ForeDrawList: + ## If an object was just added to the Foreground, there might not yet be a buffer + if self._ForegroundBuffer is None: + self._ForegroundBuffer = wx.EmptyBitmap(self.PanelSize[0], + self.PanelSize[1]) + + dc = wx.MemoryDC() ## I got some strange errors (linewidths wrong) if I didn't make a new DC here + dc.SelectObject(self._ForegroundBuffer) + dc.DrawBitmap(self._Buffer,0,0) + if self._ForegroundHTdc is None: + self._ForegroundHTdc = wx.MemoryDC() + self._ForegroundHTdc.SelectObject( wx.EmptyBitmap( + self.PanelSize[0], + self.PanelSize[1]) ) + if self._HTdc: + ## blit the background HT buffer to the foreground HT buffer + self._ForegroundHTdc.Blit(0, 0, + self.PanelSize[0], self.PanelSize[1], + self._HTdc, 0, 0) + self._DrawObjects(dc, + self._ForeDrawList, + ScreenDC, + ViewPortBB, + self._ForegroundHTdc) + ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0) +## wx.GetApp().Yield(True) + # If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn + # This seeems out of place, but it works. + if self.PrevRBBox: + ScreenDC.SetPen(wx.Pen('WHITE', 2,wx.SHORT_DASH)) + ScreenDC.SetBrush(wx.TRANSPARENT_BRUSH) + ScreenDC.SetLogicalFunction(wx.XOR) + ScreenDC.DrawRectanglePointSize(*self.PrevRBBox) + if self.Debug: print "Drawing took %f seconds of CPU time"%(clock()-start) + + ## Clear the font cache + ## IF you don't do this, the X font server starts to take up Massive amounts of memory + ## This is mostly a problem with very large fonts, that you get with scaled text when zoomed in. + DrawObject.FontList = {} + + def _ShouldRedraw(DrawList, ViewPortBB): # lrk: adapted code from BBCheck + # lrk: Returns the objects that should be redrawn + + BB2 = ViewPortBB + redrawlist = [] + for Object in DrawList: + BB1 = Object.BoundingBox + if (BB1[1,0] > BB2[0,0] and BB1[0,0] < BB2[1,0] and + BB1[1,1] > BB2[0,1] and BB1[0,1] < BB2[1,1]): + redrawlist.append(Object) + return redrawlist + _ShouldRedraw = staticmethod(_ShouldRedraw) + + +## def BBCheck(self, BB1, BB2): +## """ + +## BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise + +## """ +## if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and +## (BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ): +## return True +## else: +## return False + + def Move(self,shift,CoordType): + """ + move the image in the window. + + shift is an (x,y) tuple, specifying the amount to shift in each direction + + It can be in any of three coordinates: Panel, Pixel, World, + specified by the CoordType parameter + + Panel coordinates means you want to shift the image by some + fraction of the size of the displaed image + + Pixel coordinates means you want to shift the image by some number of pixels + + World coordinates mean you want to shift the image by an amount + in Floating point world coordinates + + """ + + shift = array(shift,Float) + if CoordType == 'Panel':# convert from panel coordinates + shift = shift * array((-1,1),Float) *self.PanelSize/self.TransformVector + elif CoordType == 'Pixel': # convert from pixel coordinates + shift = shift/self.TransformVector + elif CoordType == 'World': # No conversion + pass + else: + raise FloatCanvasException('CoordType must be either "Panel", "Pixel", or "World"') + + self.ViewPortCenter = self.ViewPortCenter + shift + self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter) + self.TransformVector = array((self.Scale,-self.Scale),Float) * self.MapProjectionVector + self._BackgroundDirty = True + self.Draw() + + def Zoom(self,factor,center = None): + + """ + Zoom(factor, center) changes the amount of zoom of the image by factor. + If factor is greater than one, the image gets larger. + If factor is less than one, the image gets smaller. + + Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming. + If center is not given, the center will stay the same. + + """ + self.Scale = self.Scale*factor + if not center is None: + self.ViewPortCenter = array(center,Float) + self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter) + self.TransformVector = array((self.Scale,-self.Scale),Float) * self.MapProjectionVector + self._BackgroundDirty = True + self.Draw() + + def ZoomToBB(self, NewBB = None, DrawFlag = True): + + """ + + Zooms the image to the bounding box given, or to the bounding + box of all the objects on the canvas, if none is given. + + """ + + if not NewBB is None: + BoundingBox = NewBB + else: + if self.BoundingBoxDirty: + self._ResetBoundingBox() + BoundingBox = self.BoundingBox + if not BoundingBox is None: + self.ViewPortCenter = array(((BoundingBox[0,0]+BoundingBox[1,0])/2, + (BoundingBox[0,1]+BoundingBox[1,1])/2 ),Float) + self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter) + # Compute the new Scale + BoundingBox = BoundingBox * self.MapProjectionVector + try: + self.Scale = min(abs(self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0])), + abs(self.PanelSize[1] / (BoundingBox[1,1]-BoundingBox[0,1])) )*0.95 + except ZeroDivisionError: # this will happen if the BB has zero width or height + try: #width == 0 + self.Scale = (self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0]))*0.95 + except ZeroDivisionError: + try: # height == 0 + self.Scale = (self.PanelSize[1] / (BoundingBox[1,1]-BoundingBox[0,1]))*0.95 + except ZeroDivisionError: #zero size! (must be a single point) + self.Scale = 1 + + self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector + if DrawFlag: + self._BackgroundDirty = True + self.Draw() + else: + # Reset the shifting and scaling to defaults when there is no BB + self.ViewPortCenter= array( (0,0), Float) + self.MapProjectionVector = array( (1,1), Float) # No Projection to start! + self.TransformVector = array( (1,-1), Float) # default Transformation + self.Scale = 1 + + def RemoveObjects(self, Objects): + for Object in Objects: + self.RemoveObject(Object, ResetBB = False) + self.BoundingBoxDirty = True + + def RemoveObject(self, Object, ResetBB = True): + ##fixme: Using the list.remove method is kind of slow + if Object.InForeground: + self._ForeDrawList.remove(Object) + else: + self._DrawList.remove(Object) + self._BackgroundDirty = True + if ResetBB: + self.BoundingBoxDirty = True + + def ClearAll(self, ResetBB = True): + self._DrawList = [] + self._ForeDrawList = [] + self._BackgroundDirty = True + self.HitColorGenerator = None + self.UseHitTest = False + if ResetBB: + self._ResetBoundingBox() + self.MakeNewBuffers() + self.HitDict = None + +## No longer called +## def _AddBoundingBox(self,NewBB): +## if self.BoundingBox is None: +## self.BoundingBox = NewBB +## self.ZoomToBB(NewBB,DrawFlag = False) +## else: +## self.BoundingBox = array( ( (min(self.BoundingBox[0,0],NewBB[0,0]), +## min(self.BoundingBox[0,1],NewBB[0,1])), +## (max(self.BoundingBox[1,0],NewBB[1,0]), +## max(self.BoundingBox[1,1],NewBB[1,1]))), +## Float) + + def _getboundingbox(bboxarray): # lrk: added this + + upperleft = minimum.reduce(bboxarray[:,0]) + lowerright = maximum.reduce(bboxarray[:,1]) + return array((upperleft, lowerright), Float) + + _getboundingbox = staticmethod(_getboundingbox) + + def _ResetBoundingBox(self): + if self._DrawList or self._ForeDrawList: + bboxarray = zeros((len(self._DrawList)+len(self._ForeDrawList), 2, 2),Float) + i = -1 # just in case _DrawList is empty + for (i, BB) in enumerate(self._DrawList): + bboxarray[i] = BB.BoundingBox + for (j, BB) in enumerate(self._ForeDrawList): + bboxarray[i+j+1] = BB.BoundingBox + self.BoundingBox = self._getboundingbox(bboxarray) + else: + self.BoundingBox = None + self.ViewPortCenter= array( (0,0), Float) + self.TransformVector = array( (1,-1), Float) + self.MapProjectionVector = array( (1,1), Float) + self.Scale = 1 + self.BoundingBoxDirty = False + + def PixelToWorld(self,Points): + """ + Converts coordinates from Pixel coordinates to world coordinates. + + Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates. + + """ + return (((asarray(Points,Float) - (self.PanelSize/2))/self.TransformVector) + self.ViewPortCenter) + + def WorldToPixel(self,Coordinates): + """ + This function will get passed to the drawing functions of the objects, + to transform from world to pixel coordinates. + Coordinates should be a NX2 array of (x,y) coordinates, or + a 2-tuple, or sequence of 2-tuples. + """ + #Note: this can be called by users code for various reasons, so asarray is needed. + return (((asarray(Coordinates,Float) - self.ViewPortCenter)*self.TransformVector)+(self.HalfPanelSize)).astype('i') + + def ScaleWorldToPixel(self,Lengths): + """ + This function will get passed to the drawing functions of the objects, + to Change a length from world to pixel coordinates. + + Lengths should be a NX2 array of (x,y) coordinates, or + a 2-tuple, or sequence of 2-tuples. + """ + return ( (asarray(Lengths,Float)*self.TransformVector) ).astype('i') + + def ScalePixelToWorld(self,Lengths): + """ + This function computes a pair of x.y lengths, + to change then from pixel to world coordinates. + + Lengths should be a NX2 array of (x,y) coordinates, or + a 2-tuple, or sequence of 2-tuples. + """ + + return (asarray(Lengths,Float) / self.TransformVector) + + def AddObject(self,obj): + # put in a reference to the Canvas, so remove and other stuff can work + obj._Canvas = self + if obj.InForeground: + self._ForeDrawList.append(obj) + self.UseForeground = True + else: + self._DrawList.append(obj) + self._BackgroundDirty = True + self.BoundingBoxDirty = True + return True + + def _DrawObjects(self, dc, DrawList, ScreenDC, ViewPortBB, HTdc = None): + """ + This is a convenience function; + This function takes the list of objects and draws them to specified + device context. + """ + dc.SetBackground(self.BackgroundBrush) + dc.BeginDrawing() + #i = 0 + PanelSize0, PanelSize1 = self.PanelSize # for speed + WorldToPixel = self.WorldToPixel # for speed + ScaleWorldToPixel = self.ScaleWorldToPixel # for speed + Blit = ScreenDC.Blit # for speed + NumBetweenBlits = self.NumBetweenBlits # for speed + for i, Object in enumerate(self._ShouldRedraw(DrawList, ViewPortBB)): + Object._Draw(dc, WorldToPixel, ScaleWorldToPixel, HTdc) + if i % NumBetweenBlits == 0: + Blit(0, 0, PanelSize0, PanelSize1, dc, 0, 0) + dc.EndDrawing() + +## ## This is a way to automatically add a AddObject method for each +## ## object type This code has been replaced by Leo's code above, so +## ## that it happens at module init, rather than as needed. The +## ## primary advantage of this is that dir(FloatCanvas) will have +## ## them, and docstrings are preserved. Probably more useful +## ## exceptions if there is a problem, as well. +## def __getattr__(self, name): +## if name[:3] == "Add": +## func=globals()[name[3:]] +## def AddFun(*args, **kwargs): +## Object = func(*args, **kwargs) +## self.AddObject(Object) +## return Object +## ## add it to FloatCanvas' dict for future calls. +## self.__dict__[name] = AddFun +## return AddFun +## else: +## raise AttributeError("FloatCanvas has no attribute '%s'"%name) + +def _makeFloatCanvasAddMethods(): ## lrk's code for doing this in module __init__ + classnames = ["Circle", "Ellipse", "Rectangle", "ScaledText", "Polygon", + "Line", "Text", "PointSet"] + for classname in classnames: + klass = globals()[classname] + def getaddshapemethod(klass=klass): + def addshape(self, *args, **kwargs): + Object = klass(*args, **kwargs) + self.AddObject(Object) + return Object + return addshape + addshapemethod = getaddshapemethod() + methodname = "Add" + classname + setattr(FloatCanvas, methodname, addshapemethod) + docstring = "Creates %s and adds its reference to the canvas.\n" % classname + docstring += "Argument protocol same as %s class" % classname + if klass.__doc__: + docstring += ", whose docstring is:\n%s" % klass.__doc__ + FloatCanvas.__dict__[methodname].__doc__ = docstring + +_makeFloatCanvasAddMethods() + + diff --git a/wxPython/wx/lib/floatcanvas/NavCanvas.py b/wxPython/wx/lib/floatcanvas/NavCanvas.py new file mode 100644 index 0000000000..5d7ee0cadc --- /dev/null +++ b/wxPython/wx/lib/floatcanvas/NavCanvas.py @@ -0,0 +1,125 @@ +""" +A Panel that includes the FloatCanvas and Navigation controls + +""" + +#from Numeric import array,Float,cos,pi,sum,minimum,maximum,Int32 + +#from time import clock, sleep + +import wx + +#import types +#import os + +import FloatCanvas, Resources + + +ID_ZOOM_IN_BUTTON = wx.NewId() +ID_ZOOM_OUT_BUTTON = wx.NewId() +ID_ZOOM_TO_FIT_BUTTON = wx.NewId() +ID_MOVE_MODE_BUTTON = wx.NewId() +ID_POINTER_BUTTON = wx.NewId() + + +#--------------------------------------------------------------------------- + +class NavCanvas(wx.Panel): + """ + NavCanvas.py + + This is a high level window that encloses the FloatCanvas in a panel + and adds a Navigation toolbar. + + Copyright: wxWindows Software Foundation (Assigned by: Christopher Barker) + + License: Same as the version of wxPython you are using it with + + Please let me know if you're using this!!! + + Contact me at: + + Chris.Barker@noaa.gov + + """ + + def __init__(self, parent, id = -1, + size = wx.DefaultSize, + **kwargs): # The rest just get passed into FloatCanvas + + wx.Panel.__init__( self, parent, id, wx.DefaultPosition, size) + + ## Create the vertical sizer for the toolbar and Panel + box = wx.BoxSizer(wx.VERTICAL) + box.Add(self.BuildToolbar(), 0, wx.ALL | wx.ALIGN_LEFT | wx.GROW, 4) + + self.Canvas = FloatCanvas.FloatCanvas( self, wx.NewId(), + size = wx.DefaultSize, + **kwargs) + box.Add(self.Canvas,1,wx.GROW) + + box.Fit(self) + self.SetSizer(box) + + # default to Mouse mode + self.ToolBar.ToggleTool(ID_POINTER_BUTTON,1) + self.Canvas.SetMode("Mouse") + + return None + + def __getattr__(self, name): + """ + Delegate all extra methods to the Canvas + """ + attrib = getattr(self.Canvas, name) + ## add the attribute to this module's dict for future calls + self.__dict__[name] = attrib + return attrib + + def BuildToolbar(self): + tb = wx.ToolBar(self,-1) + self.ToolBar = tb + + tb.SetToolBitmapSize((23,23)) + + tb.AddTool(ID_POINTER_BUTTON, Resources.GetPointerBitmap(), isToggle=True, shortHelpString = "Pointer") + wx.EVT_TOOL(self, ID_POINTER_BUTTON, self.SetToolMode) + + tb.AddTool(ID_ZOOM_IN_BUTTON, Resources.GetPlusBitmap(), isToggle=True, shortHelpString = "Zoom In") + wx.EVT_TOOL(self, ID_ZOOM_IN_BUTTON, self.SetToolMode) + + tb.AddTool(ID_ZOOM_OUT_BUTTON, Resources.GetMinusBitmap(), isToggle=True, shortHelpString = "Zoom Out") + wx.EVT_TOOL(self, ID_ZOOM_OUT_BUTTON, self.SetToolMode) + + tb.AddTool(ID_MOVE_MODE_BUTTON, Resources.GetHandBitmap(), isToggle=True, shortHelpString = "Move") + wx.EVT_TOOL(self, ID_MOVE_MODE_BUTTON, self.SetToolMode) + + tb.AddSeparator() + + tb.AddControl(wx.Button(tb, ID_ZOOM_TO_FIT_BUTTON, "Zoom To Fit",wx.DefaultPosition, wx.DefaultSize)) + wx.EVT_BUTTON(self, ID_ZOOM_TO_FIT_BUTTON, self.ZoomToFit) + + tb.Realize() + tb.SetSizeHints(tb.GetSize()) + return tb + + def SetToolMode(self,event): + for id in [ID_ZOOM_IN_BUTTON, + ID_ZOOM_OUT_BUTTON, + ID_MOVE_MODE_BUTTON, + ID_POINTER_BUTTON]: + self.ToolBar.ToggleTool(id,0) + self.ToolBar.ToggleTool(event.GetId(),1) + if event.GetId() == ID_ZOOM_IN_BUTTON: + self.Canvas.SetMode("ZoomIn") + elif event.GetId() == ID_ZOOM_OUT_BUTTON: + self.Canvas.SetMode("ZoomOut") + elif event.GetId() == ID_MOVE_MODE_BUTTON: + self.Canvas.SetMode("Move") + elif event.GetId() == ID_POINTER_BUTTON: + self.Canvas.SetMode("Mouse") + + + def ZoomToFit(self,Event): + self.Canvas.ZoomToBB() + diff --git a/wxPython/wx/lib/floatcanvas/Resources.py b/wxPython/wx/lib/floatcanvas/Resources.py new file mode 100644 index 0000000000..de0a4f3db6 --- /dev/null +++ b/wxPython/wx/lib/floatcanvas/Resources.py @@ -0,0 +1,66 @@ +""" +Resources.py Various resources needed by the FloatCanvas package + +Includes, icons, etc. + +""" + + +### These are some functions for bitmaps of icons. +import wx, cPickle, zlib + +def GetHandData(): + return cPickle.loads(zlib.decompress( +'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\ +\x01\xc8S\xb6t\x06A(\x1f\x0b\xa0\xa9\x8c\x9e\x1e6\x19\xa0\xa8\x1e\x88\xd4C\ +\x97\xd1\x83\xe8\x80 \x9c2zh\xa6\xc1\x11X\n\xab\x8c\x02\x8a\x0cD!\x92\x12\ +\x98\x8c\x1e\x8a\x8b\xd1d\x14\xf4\x90%\x90LC\xf6\xbf\x1e\xba\xab\x91%\xd0\ +\xdc\x86C\x06\xd9m\xe8!\xaa\x87S\x86\x1a1\xa7\x07\x00v\x0f[\x17' )) + +def GetHandBitmap(): + return wx.BitmapFromXPMData(GetHandData()) + +#---------------------------------------------------------------------- +def GetPlusData(): + return cPickle.loads(zlib.decompress( +'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\ +\x01\xc8S\xb6t\x06A(\x1f\x0b RF\x0f\x08\xb0\xc9@D\xe1r\x08\x19\xb8j=l2`\r\ +\xe82HF\xe9a\xc8\xe8\xe9A\x9c@\x8a\x0c\x0e\xd3p\xbb\x00\x8f\xab\xe1>\xd5\xd3\ +\xc3\x15:P)l!\n\x91\xc2\x1a\xd6`)\xec\xb1\x00\x92\xc2\x11?\xb8e\x88\x8fSt\ +\x19=\x00\x82\x16[\xf7' )) + +def GetPlusBitmap(): + return wx.BitmapFromXPMData(GetPlusData()) + +#---------------------------------------------------------------------- +def GetMinusData(): + return cPickle.loads(zlib.decompress( +'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\ +\x01\xc8S\xb6t\x06A(\x1f\x0b RF\x0f\x08\xb0\xc9@D\xe1r\x08\x19\xb8j=\xa2e\ +\x10\x16@\x99\xc82zz\x10\'\x90"\x83\xc34r\xdc\x86\xf0\xa9\x9e\x1e\xae\xd0\ +\x81Ja\x0bQ\x88\x14\xd6\xb0\x06Ka\x8f\x05\x90\x14\x8e\xf8\xc1-C|\x9c\xa2\xcb\ +\xe8\x01\x00\xed\x0f[\x87' )) + +def GetMinusBitmap(): + return wx.BitmapFromXPMData(GetMinusData()) + +## NOTE: this was created using a newer version of img2py than the above +import cStringIO +def GetPointerData(): + return zlib.decompress( +'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\xa2 \xcc\xc1\ +\x06$W\x8a/\x9d\x06\xa4X\x8a\x9d