/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/view.py
ViewVC logotype

Diff of /branches/WIP-pyshapelib-bramz/Thuban/UI/view.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 303 by bh, Mon Sep 2 16:47:53 2002 UTC revision 1552 by bh, Wed Aug 6 17:21:32 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002 by Intevation GmbH  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2  # Authors:  # Authors:
3  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
4    # Frank Koormann <[email protected]>
5  #  #
6  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
7  # Read the file COPYING coming with Thuban for details.  # Read the file COPYING coming with Thuban for details.
# Line 11  Classes for display of a map and interac Line 12  Classes for display of a map and interac
12    
13  __version__ = "$Revision$"  __version__ = "$Revision$"
14    
15  from math import hypot  from Thuban import _
16    
17  from wxPython.wx import wxWindow,\  import os.path
      wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\  
      EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW  
18    
19    from wxPython.wx import wxWindow, \
20         wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
21         EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
22         wxPlatform, wxBeginBusyCursor, wxEndBusyCursor, wxFileDialog, wxSAVE, \
23         wxOVERWRITE_PROMPT, wxID_OK
24    
25    # Export related stuff
26    if wxPlatform == '__WXMSW__':
27        from wxPython.wx import wxMetaFileDC
28    
29  from wxPython import wx  from wxPython import wx
30    
31  from wxproj import point_in_polygon_shape, shape_centroid  from Thuban.Model.messages import MAP_LAYERS_CHANGED, LAYER_CHANGED, \
32         LAYER_VISIBILITY_CHANGED
33    
34  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \  from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
      LAYERS_CHANGED, LAYER_LEGEND_CHANGED, LAYER_VISIBILITY_CHANGED  
 from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \  
      SHAPETYPE_POINT  
 from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \  
      ALIGN_LEFT, ALIGN_RIGHT  
 from Thuban.Lib.connector import Publisher  
   
 from renderer import ScreenRenderer, PrinterRender  
35    
36  import labeldialog  import labeldialog
37    
38  from messages import SELECTED_SHAPE, VIEW_POSITION  from viewport import ViewPort, PanTool, output_transform
   
   
 #  
 #   The tools  
 #  
   
 class Tool:  
   
     """  
     Base class for the interactive tools  
     """  
39    
40      def __init__(self, view):  class CanvasPanTool(PanTool):
         """Intitialize the tool. The view is the canvas displaying the map"""  
         self.view = view  
         self.start = self.current = None  
         self.dragging = 0  
         self.drawn = 0  
   
     def Name(self):  
         """Return the tool's name"""  
         return ''  
   
     def drag_start(self, x, y):  
         self.start = self.current = x, y  
         self.dragging = 1  
   
     def drag_move(self, x, y):  
         self.current = x, y  
   
     def drag_stop(self, x, y):  
         self.current = x, y  
         self.dragging = 0  
   
     def Show(self, dc):  
         if not self.drawn:  
             self.draw(dc)  
         self.drawn = 1  
   
     def Hide(self, dc):  
         if self.drawn:  
             self.draw(dc)  
         self.drawn = 0  
41    
42      def draw(self, dc):      """The Canvas Pan Tool"""
         pass  
   
     def MouseDown(self, event):  
         self.drag_start(event.m_x, event.m_y)  
43    
44      def MouseMove(self, event):      def MouseMove(self, event):
45          if self.dragging:          if self.dragging:
46              self.drag_move(event.m_x, event.m_y)              PanTool.MouseMove(self, event)
   
     def MouseUp(self, event):  
         if self.dragging:  
             self.drag_move(event.m_x, event.m_y)  
   
     def Cancel(self):  
         self.dragging = 0  
   
   
 class RectTool(Tool):  
   
     """Base class for tools that draw rectangles while dragging"""  
   
     def draw(self, dc):  
         sx, sy = self.start  
         cx, cy = self.current  
         dc.DrawRectangle(sx, sy, cx - sx, cy - sy)  
   
 class ZoomInTool(RectTool):  
   
     """The Zoom-In Tool"""  
   
     def Name(self):  
         return "ZoomInTool"  
   
     def proj_rect(self):  
         """return the rectangle given by start and current in projected  
         coordinates"""  
         sx, sy = self.start  
         cx, cy = self.current  
         left, top = self.view.win_to_proj(sx, sy)  
         right, bottom = self.view.win_to_proj(cx, cy)  
         return (min(left, right), min(top, bottom),  
                 max(left, right), max(top, bottom))  
   
     def MouseUp(self, event):  
         if self.dragging:  
             Tool.MouseUp(self, event)  
             sx, sy = self.start  
             cx, cy = self.current  
             if sx == cx or sy == cy:  
                 # Just a mouse click or a degenerate rectangle. Simply  
                 # zoom in by a factor of two  
                 # FIXME: For a click this is the desired behavior but should we  
                 # really do this for degenrate rectagles as well or  
                 # should we ignore them?  
                 self.view.ZoomFactor(2, center = (cx, cy))  
             else:  
                 # A drag. Zoom in to the rectangle  
                 self.view.FitRectToWindow(self.proj_rect())  
   
   
 class ZoomOutTool(RectTool):  
   
     """The Zoom-Out Tool"""  
   
     def Name(self):  
         return "ZoomOutTool"  
   
     def MouseUp(self, event):  
         if self.dragging:  
             Tool.MouseUp(self, event)  
             sx, sy = self.start  
             cx, cy = self.current  
             if sx == cx or sy == cy:  
                 # Just a mouse click or a degenerate rectangle. Simply  
                 # zoom out by a factor of two.  
                 # FIXME: For a click this is the desired behavior but should we  
                 # really do this for degenrate rectagles as well or  
                 # should we ignore them?  
                 self.view.ZoomFactor(0.5, center = (cx, cy))  
             else:  
                 # A drag. Zoom out to the rectangle  
                 self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),  
                                          max(sx, cx), max(sy, cy)))  
   
   
 class PanTool(Tool):  
   
     """The Pan Tool"""  
   
     def Name(self):  
         return "PanTool"  
   
     def MouseMove(self, event):  
         if self.dragging:  
             Tool.MouseMove(self, event)  
47              sx, sy = self.start              sx, sy = self.start
48              x, y = self.current              x, y = self.current
49              width, height = self.view.GetSizeTuple()              width, height = self.view.GetSizeTuple()
# Line 186  class PanTool(Tool): Line 54  class PanTool(Tool):
54              dc = self.view.drag_dc              dc = self.view.drag_dc
55              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
56    
     def MouseUp(self, event):  
         if self.dragging:  
             Tool.MouseUp(self, event)  
             sx, sy = self.start  
             cx, cy = self.current  
             self.view.Translate(cx - sx, cy - sy)  
   
 class IdentifyTool(Tool):  
   
     """The "Identify" Tool"""  
   
     def Name(self):  
         return "IdentifyTool"  
   
     def MouseUp(self, event):  
         self.view.SelectShapeAt(event.m_x, event.m_y)  
   
   
 class LabelTool(Tool):  
   
     """The "Label" Tool"""  
   
     def Name(self):  
         return "LabelTool"  
   
     def MouseUp(self, event):  
         self.view.LabelShapeAt(event.m_x, event.m_y)  
   
   
   
   
57  class MapPrintout(wx.wxPrintout):  class MapPrintout(wx.wxPrintout):
58    
59      """      """
60      wxPrintout class for printing Thuban maps      wxPrintout class for printing Thuban maps
61      """      """
62    
63      def __init__(self, map):      def __init__(self, canvas, map, region, selected_layer, selected_shapes):
64          wx.wxPrintout.__init__(self)          wx.wxPrintout.__init__(self)
65            self.canvas = canvas
66          self.map = map          self.map = map
67            self.region = region
68            self.selected_layer = selected_layer
69            self.selected_shapes = selected_shapes
70    
71      def GetPageInfo(self):      def GetPageInfo(self):
72          return (1, 1, 1, 1)          return (1, 1, 1, 1)
# Line 239  class MapPrintout(wx.wxPrintout): Line 80  class MapPrintout(wx.wxPrintout):
80    
81      def draw_on_dc(self, dc):      def draw_on_dc(self, dc):
82          width, height = self.GetPageSizePixels()          width, height = self.GetPageSizePixels()
83          llx, lly, urx, ury = self.map.ProjectedBoundingBox()          scale, offset, mapregion = output_transform(self.canvas.scale,
84          scalex = width / (urx - llx)                                                      self.canvas.offset,
85          scaley = height / (ury - lly)                                                      self.canvas.GetSizeTuple(),
86          scale = min(scalex, scaley)                                                      self.GetPageSizePixels())
         offx = 0.5 * (width - (urx + llx) * scale)  
         offy = 0.5 * (height + (ury + lly) * scale)  
   
87          resx, resy = self.GetPPIPrinter()          resx, resy = self.GetPPIPrinter()
88          renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)          renderer = PrinterRenderer(dc, scale, offset, resolution = resy)
89          renderer.RenderMap(self.map)          x, y, width, height = self.region
90          return wx.true          canvas_scale = self.canvas.scale
91            renderer.RenderMap(self.map,
92                               (0,0,
93                                    (width/canvas_scale)*scale,
94                                    (height/canvas_scale)*scale),
95                                    mapregion,
96                               self.selected_layer, self.selected_shapes)
97            return True
98    
99  class MapCanvas(wxWindow, Publisher):  class MapCanvas(wxWindow, ViewPort):
100    
101      """A widget that displays a map and offers some interaction"""      """A widget that displays a map and offers some interaction"""
102    
103      def __init__(self, parent, winid, interactor):      def __init__(self, parent, winid):
104          wxWindow.__init__(self, parent, winid)          wxWindow.__init__(self, parent, winid)
105          self.SetBackgroundColour(wxColour(255, 255, 255))          ViewPort.__init__(self)
   
         # the map displayed in this canvas. Set with SetMap()  
         self.map = None  
106    
107          # scale and offset describe the transformation from projected          self.SetBackgroundColour(wxColour(255, 255, 255))
         # coordinates to window coordinates.  
         self.scale = 1.0  
         self.offset = (0, 0)  
   
         # whether the user is currently dragging the mouse, i.e. moving  
         # the mouse while pressing a mouse button  
         self.dragging = 0  
   
         # the currently active tool  
         self.tool = None  
   
         # The current mouse position of the last OnMotion event or None  
         # if the mouse is outside the window.  
         self.current_position = None  
108    
109          # the bitmap serving as backing store          # the bitmap serving as backing store
110          self.bitmap = None          self.bitmap = None
111    
112          # the interactor          self.backgroundColor = wx.wxWHITE_BRUSH
113          self.interactor = interactor  
114          self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)          # Set to true if there ever is an error during redraw. There
115            # should never be errors, but unfortunately bugs happen.
116          # keep track of which layers/shapes are selected to make sure we          self.error_on_redraw = 0
         # only redraw when necessary  
         self.last_selected_layer = None  
         self.last_selected_shape = None  
117    
118          # subscribe the WX events we're interested in          # subscribe the WX events we're interested in
119          EVT_PAINT(self, self.OnPaint)          EVT_PAINT(self, self.OnPaint)
# Line 298  class MapCanvas(wxWindow, Publisher): Line 122  class MapCanvas(wxWindow, Publisher):
122          EVT_MOTION(self, self.OnMotion)          EVT_MOTION(self, self.OnMotion)
123          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
124          wx.EVT_SIZE(self, self.OnSize)          wx.EVT_SIZE(self, self.OnSize)
125            wx.EVT_IDLE(self, self.OnIdle)
126    
127      def __del__(self):      def __del__(self):
128          wxWindow.__del__(self)          wxWindow.__del__(self)
129          Publisher.__del__(self)          ViewPort.__del__(self)
130    
131        def PanTool(self):
132            """Start the canvas pan tool"""
133            self.SelectTool(CanvasPanTool(self))
134            
135        def SetMap(self, map):
136            redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
137                               LAYER_VISIBILITY_CHANGED)
138            if self.Map() is not None:
139                for channel in redraw_channels:
140                    self.Map().Unsubscribe(channel, self.full_redraw)
141    
142            ViewPort.SetMap(self, map)
143    
144            if self.Map() is not None:
145                for channel in redraw_channels:
146                    self.Map().Subscribe(channel, self.full_redraw)
147    
148            # force a redraw. If map is not empty, it's already been called
149            # by FitMapToWindow but if map is empty it hasn't been called
150            # yet so we have to explicitly call it.
151            self.full_redraw()
152    
153      def OnPaint(self, event):      def OnPaint(self, event):
154          dc = wxPaintDC(self)          dc = wxPaintDC(self)
155          if self.map is not None and self.map.HasLayers():  
156              self.do_redraw()          if self.Map() is not None and self.Map().HasLayers():
157                if self.bitmap in (None, -1):
158                    # set the flag that we should redraw the
159                    # bitmap in idle time
160                    self.bitmap = -1
161                else:
162                    # blit the bitmap to the screen
163                    dc.BeginDrawing()
164                    dc.DrawBitmap(self.bitmap, 0, 0)
165                    dc.EndDrawing()
166          else:          else:
167              # If we've got no map or if the map is empty, simply clear              # If we've got no map or if the map is empty, simply clear
168              # the screen.              # the screen.
# Line 316  class MapCanvas(wxWindow, Publisher): Line 172  class MapCanvas(wxWindow, Publisher):
172              # only thing we may have to do is to call self.Refresh()              # only thing we may have to do is to call self.Refresh()
173              # with a true argument in the right places.              # with a true argument in the right places.
174              dc.BeginDrawing()              dc.BeginDrawing()
175                dc.SetBackground(self.backgroundColor)
176              dc.Clear()              dc.Clear()
177              dc.EndDrawing()              dc.EndDrawing()
178    
179      def do_redraw(self):      def OnIdle(self, event):
180          # This should only be called if we have a non-empty map.          """Idle handler. Redraw the bitmap if necessary"""
   
         # Get the window size.  
         width, height = self.GetSizeTuple()  
   
         # If self.bitmap's still there, reuse it. Otherwise redraw it  
         if self.bitmap is not None:  
             bitmap = self.bitmap  
         else:  
             bitmap = wx.wxEmptyBitmap(width, height)  
             dc = wx.wxMemoryDC()  
             dc.SelectObject(bitmap)  
             dc.BeginDrawing()  
181    
182              # clear the background          if self.bitmap != -1:
183              dc.SetBrush(wx.wxWHITE_BRUSH)              return
184              dc.SetPen(wx.wxTRANSPARENT_PEN)          if self.error_on_redraw:
185              dc.DrawRectangle(0, 0, width, height)              return
   
             if 1: #self.interactor.selected_map is self.map:  
                 selected_layer = self.interactor.selected_layer  
                 selected_shape = self.interactor.selected_shape  
             else:  
                 selected_layer = None  
                 selected_shape = None  
   
             # draw the map into the bitmap  
             renderer = ScreenRenderer(dc, self.scale, self.offset)  
186    
187              # Pass the entire bitmap as update region to the renderer.          wxBeginBusyCursor()
188              # We're redrawing the whole bitmap, after all.          try:
189              renderer.RenderMap(self.map, (0, 0, width, height),              try:
190                                 selected_layer, selected_shape)                  self._do_redraw()
191                except:
192                    self.error_on_redraw = True
193                    raise
194            finally:
195                wxEndBusyCursor()
196    
197              dc.EndDrawing()      def _do_redraw(self):
198              dc.SelectObject(wx.wxNullBitmap)          """Called by OnIdle to do the actual redraw.
199              self.bitmap = bitmap          """
200            width, height = self.GetSizeTuple()
201    
202          # blit the bitmap to the screen          bitmap = wx.wxEmptyBitmap(width, height)
203          dc = wx.wxMemoryDC()          dc = wx.wxMemoryDC()
204          dc.SelectObject(bitmap)          dc.SelectObject(bitmap)
205          clientdc = wxClientDC(self)          dc.BeginDrawing()
         clientdc.BeginDrawing()  
         clientdc.Blit(0, 0, width, height, dc, 0, 0)  
         clientdc.EndDrawing()  
206    
207      def Print(self):          dc.SetBackground(self.backgroundColor)
208          printer = wx.wxPrinter()          dc.Clear()
         printout = MapPrintout(self.map)  
         printer.Print(self, printout, wx.true)  
         printout.Destroy()  
209    
210      def SetMap(self, map):          selected_layer = self.selection.SelectedLayer()
211          redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,          selected_shapes = self.selection.SelectedShapes()
                            LAYER_VISIBILITY_CHANGED)  
         if self.map is not None:  
             for channel in redraw_channels:  
                 self.map.Unsubscribe(channel, self.full_redraw)  
             self.map.Unsubscribe(MAP_PROJECTION_CHANGED,  
                                  self.projection_changed)  
         self.map = map  
         if self.map is not None:  
             for channel in redraw_channels:  
                 self.map.Subscribe(channel, self.full_redraw)  
             self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)  
         self.FitMapToWindow()  
         # force a redraw. If map is not empty, it's already been called  
         # by FitMapToWindow but if map is empty it hasn't been called  
         # yet so we have to explicitly call it.  
         self.full_redraw()  
212    
213      def Map(self):          # draw the map into the bitmap
214          """Return the map displayed by this canvas"""          renderer = ScreenRenderer(dc, self.scale, self.offset)
         return self.map  
215    
216      def redraw(self, *args):          # Pass the entire bitmap as update region to the renderer.
217          self.Refresh(0)          # We're redrawing the whole bitmap, after all.
218            renderer.RenderMap(self.Map(), (0, 0, width, height),
219                               selected_layer, selected_shapes)
220    
221      def full_redraw(self, *args):          dc.EndDrawing()
222          self.bitmap = None          dc.SelectObject(wx.wxNullBitmap)
223    
224            self.bitmap = bitmap
225            # This causes a paint event that then draws the bitmap
226          self.redraw()          self.redraw()
227    
228      def projection_changed(self, *args):      def Export(self):
         self.FitMapToWindow()  
         self.full_redraw()  
229    
230      def set_view_transform(self, scale, offset):          if hasattr(self, "export_path"):
231          self.scale = scale              export_path = self.export_path
232          self.offset = offset          else:
233          self.full_redraw()              export_path="."
234            dlg = wxFileDialog(self, _("Export Map"), export_path, "",
235                               "Enhanced Metafile (*.wmf)|*.wmf",
236                               wxSAVE|wxOVERWRITE_PROMPT)
237            if dlg.ShowModal() == wxID_OK:
238                self.export_path = os.path.dirname(dlg.GetPath())
239                dc = wxMetaFileDC(dlg.GetPath())
240        
241                scale, offset, mapregion = output_transform(self.scale,
242                                                            self.offset,
243                                                            self.GetSizeTuple(),
244                                                            dc.GetSizeTuple())
245    
246      def proj_to_win(self, x, y):              selected_layer = self.selection.SelectedLayer()
247          """\              selected_shapes = self.selection.SelectedShapes()
         Return the point in  window coords given by projected coordinates x y  
         """  
         offx, offy = self.offset  
         return (self.scale * x + offx, -self.scale * y + offy)  
248    
249      def win_to_proj(self, x, y):              renderer = ExportRenderer(dc, scale, offset)
         """\  
         Return the point in projected coordinates given by window coords x y  
         """  
         offx, offy = self.offset  
         return ((x - offx) / self.scale, (offy - y) / self.scale)  
250    
251      def FitRectToWindow(self, rect):              # Pass the entire bitmap as update region to the renderer.
252          """Fit the rectangular region given by rect into the window.              # We're redrawing the whole bitmap, after all.
253                width, height = self.GetSizeTuple()
254                renderer.RenderMap(self.Map(),
255                                    (0,0,
256                                        (width/self.scale)*scale,
257                                        (height/self.scale)*scale),
258                                    mapregion,
259                                    selected_layer, selected_shapes)
260                dc.EndDrawing()
261                dc.Close()
262            dlg.Destroy()
263                    
264          Set scale so that rect (in projected coordinates) just fits into      def Print(self):
265          the window and center it.          printer = wx.wxPrinter()
         """  
266          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
267          llx, lly, urx, ury = rect          selected_layer = self.selection.SelectedLayer()
268          if llx == urx or lly == ury:          selected_shapes = self.selection.SelectedShapes()
             # zero with or zero height. Do Nothing  
             return  
         scalex = width / (urx - llx)  
         scaley = height / (ury - lly)  
         scale = min(scalex, scaley)  
         offx = 0.5 * (width - (urx + llx) * scale)  
         offy = 0.5 * (height + (ury + lly) * scale)  
         self.set_view_transform(scale, (offx, offy))  
   
     def FitMapToWindow(self):  
         """Fit the map to the window  
269                    
270          Set the scale so that the map fits exactly into the window and          printout = MapPrintout(self, self.Map(), (0, 0, width, height),
271          center it in the window.                                 selected_layer, selected_shapes)
272          """          printer.Print(self, printout, True)
273          bbox = self.map.ProjectedBoundingBox()          printout.Destroy()
         if bbox is not None:  
             self.FitRectToWindow(bbox)  
   
     def ZoomFactor(self, factor, center = None):  
         """Multiply the zoom by factor and center on center.  
   
         The optional parameter center is a point in window coordinates  
         that should be centered. If it is omitted, it defaults to the  
         center of the window  
         """  
         width, height = self.GetSizeTuple()  
         scale = self.scale * factor  
         offx, offy = self.offset  
         if center is not None:  
             cx, cy = center  
         else:  
             cx = width / 2  
             cy = height / 2  
         offset = (factor * (offx - cx) + width / 2,  
                   factor * (offy - cy) + height / 2)  
         self.set_view_transform(scale, offset)  
   
     def ZoomOutToRect(self, rect):  
         """Zoom out to fit the currently visible region into rect.  
   
         The rect parameter is given in window coordinates  
         """  
         # determine the bbox of the displayed region in projected  
         # coordinates  
         width, height = self.GetSizeTuple()  
         llx, lly = self.win_to_proj(0, height - 1)  
         urx, ury = self.win_to_proj(width - 1, 0)  
   
         sx, sy, ex, ey = rect  
         scalex = (ex - sx) / (urx - llx)  
         scaley = (ey - sy) / (ury - lly)  
         scale = min(scalex, scaley)  
   
         offx = 0.5 * ((ex + sx) - (urx + llx) * scale)  
         offy = 0.5 * ((ey + sy) + (ury + lly) * scale)  
         self.set_view_transform(scale, (offx, offy))  
   
     def Translate(self, dx, dy):  
         """Move the map by dx, dy pixels"""  
         offx, offy = self.offset  
         self.set_view_transform(self.scale, (offx + dx, offy + dy))  
   
     def ZoomInTool(self):  
         """Start the zoom in tool"""  
         self.tool = ZoomInTool(self)  
   
     def ZoomOutTool(self):  
         """Start the zoom out tool"""  
         self.tool = ZoomOutTool(self)  
274    
275      def PanTool(self):      def redraw(self, *args):
276          """Start the pan tool"""          self.Refresh(False)
         self.tool = PanTool(self)  
277    
278      def IdentifyTool(self):      def full_redraw(self, *args):
279          """Start the identify tool"""          self.bitmap = None
280          self.tool = IdentifyTool(self)          self.redraw()
   
     def LabelTool(self):  
         """Start the label tool"""  
         self.tool = LabelTool(self)  
   
     def CurrentTool(self):  
         """Return the name of the current tool or None if no tool is active"""  
         return self.tool and self.tool.Name() or None  
281    
282      def CurrentPosition(self):      def map_projection_changed(self, map, old_proj):
283          """Return current position of the mouse in projected coordinates.          ViewPort.map_projection_changed(self, map, old_proj)
284            self.full_redraw()
285    
286          The result is a 2-tuple of floats with the coordinates. If the      def layer_projection_changed(self, *args):
287          mouse is not in the window, the result is None.          ViewPort.layer_projection_changed(self, args)
288          """          self.full_redraw()
         if self.current_position is not None:  
             x, y = self.current_position  
             return self.win_to_proj(x, y)  
         else:  
             return None  
289    
290      def set_current_position(self, event):      def set_view_transform(self, scale, offset):
291          """Set the current position from event          ViewPort.set_view_transform(self, scale, offset)
292            self.full_redraw()
293    
294          Should be called by all events that contain mouse positions      def GetPortSizeTuple(self):
295          especially EVT_MOTION. The event paramete may be None to          return self.GetSizeTuple()
         indicate the the pointer left the window.  
         """  
         if event is not None:  
             self.current_position = (event.m_x, event.m_y)  
         else:  
             self.current_position = None  
         self.issue(VIEW_POSITION)  
296    
297      def OnLeftDown(self, event):      def OnLeftDown(self, event):
298          self.set_current_position(event)          self.MouseLeftDown(event)
299          if self.tool is not None:          if self.tool is not None:
300              self.drag_dc = wxClientDC(self)              self.drag_dc = wxClientDC(self)
301              self.drag_dc.SetLogicalFunction(wxINVERT)              self.drag_dc.SetLogicalFunction(wxINVERT)
302              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
             self.CaptureMouse()  
             self.tool.MouseDown(event)  
303              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
304                self.CaptureMouse()
305              self.dragging = 1              self.dragging = 1
306    
307      def OnLeftUp(self, event):      def OnLeftUp(self, event):
308          self.set_current_position(event)          self.MouseLeftUp(event)
309          if self.dragging:          if self.dragging:
310              self.ReleaseMouse()              self.ReleaseMouse()
311              self.tool.Hide(self.drag_dc)              try:
312              self.tool.MouseUp(event)                  self.tool.Hide(self.drag_dc)
313              self.drag_dc = None              finally:
314          self.dragging = 0                  self.drag_dc = None
315                    self.dragging = 0
316    
317      def OnMotion(self, event):      def OnMotion(self, event):
         self.set_current_position(event)  
318          if self.dragging:          if self.dragging:
319              self.tool.Hide(self.drag_dc)              self.tool.Hide(self.drag_dc)
320              self.tool.MouseMove(event)  
321            self.MouseMove(event)
322    
323            if self.dragging:
324              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
325    
326      def OnLeaveWindow(self, event):      def OnLeaveWindow(self, event):
# Line 586  class MapCanvas(wxWindow, Publisher): Line 336  class MapCanvas(wxWindow, Publisher):
336          self.full_redraw()          self.full_redraw()
337    
338      def shape_selected(self, layer, shape):      def shape_selected(self, layer, shape):
339          """Redraw the map.          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
340            # The selection object takes care that it only issues
341          Receiver for the SELECTED_SHAPE messages. Try to redraw only          # SHAPES_SELECTED messages when the set of selected shapes has
342          when necessary.          # actually changed, so we can do a full redraw unconditionally.
343          """          # FIXME: We should perhaps try to limit the redraw to the are
344          # A redraw is necessary when the display has to change, which          # actually covered by the shapes before and after the selection
345          # means that either the status changes from having no selection          # change.
346          # to having a selection shape or vice versa, or when the fact          ViewPort.shape_selected(self, layer, shape)
347          # whether there is a selection at all doesn't change, when the          self.full_redraw()
         # shape which is selected has changed (which means that layer or  
         # shapeid changes).  
         if ((shape is not None or self.last_selected_shape is not None)  
             and (shape != self.last_selected_shape  
                  or layer != self.last_selected_layer)):  
             self.full_redraw()  
   
         # remember the selection so we can compare when it changes again.  
         self.last_selected_layer = layer  
         self.last_selected_shape = shape  
   
     def unprojected_rect_around_point(self, x, y, dist):  
         """return a rect dist pixels around (x, y) in unprojected corrdinates  
   
         The return value is a tuple (minx, miny, maxx, maxy) suitable a  
         parameter to a layer's ShapesInRegion method.  
         """  
         map_proj = self.map.projection  
         if map_proj is not None:  
             inverse = map_proj.Inverse  
         else:  
             inverse = None  
   
         xs = []  
         ys = []  
         for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):  
             px, py = self.win_to_proj(x + dist * dx, y + dist * dy)  
             if inverse:  
                 px, py = inverse(px, py)  
             xs.append(px)  
             ys.append(py)  
         return (min(xs), min(ys), max(xs), max(ys))  
   
     def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):  
         """Determine the shape at point px, py in window coords  
   
         Return the shape and the corresponding layer as a tuple (layer,  
         shape).  
   
         If the optional parameter select_labels is true (default false)  
         search through the labels. If a label is found return it's index  
         as the shape and None as the layer.  
   
         If the optional parameter searched_layer is given (or not None  
         which it defaults to), only search in that layer.  
         """  
         map_proj = self.map.projection  
         if map_proj is not None:  
             forward = map_proj.Forward  
         else:  
             forward = None  
   
         scale = self.scale  
         offx, offy = self.offset  
   
         if select_labels:  
             labels = self.map.LabelLayer().Labels()  
   
             if labels:  
                 dc = wxClientDC(self)  
                 font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)  
                 dc.SetFont(font)  
                 for i in range(len(labels) - 1, -1, -1):  
                     label = labels[i]  
                     x = label.x  
                     y = label.y  
                     text = label.text  
                     if forward:  
                         x, y = forward(x, y)  
                     x = x * scale + offx  
                     y = -y * scale + offy  
                     width, height = dc.GetTextExtent(text)  
                     if label.halign == ALIGN_LEFT:  
                         # nothing to be done  
                         pass  
                     elif label.halign == ALIGN_RIGHT:  
                         x = x - width  
                     elif label.halign == ALIGN_CENTER:  
                         x = x - width/2  
                     if label.valign == ALIGN_TOP:  
                         # nothing to be done  
                         pass  
                     elif label.valign == ALIGN_BOTTOM:  
                         y = y - height  
                     elif label.valign == ALIGN_CENTER:  
                         y = y - height/2  
                     if x <= px < x + width and y <= py <= y + height:  
                         return None, i  
   
         if searched_layer:  
             layers = [searched_layer]  
         else:  
             layers = self.map.Layers()  
   
         for layer_index in range(len(layers) - 1, -1, -1):  
             layer = layers[layer_index]  
   
             # search only in visible layers  
             if not layer.Visible():  
                 continue  
   
             filled = layer.fill is not None  
             stroked = layer.stroke is not None  
   
             layer_proj = layer.projection  
             if layer_proj is not None:  
                 inverse = layer_proj.Inverse  
             else:  
                 inverse = None  
   
             shapetype = layer.ShapeType()  
   
             select_shape = -1  
   
             # Determine the ids of the shapes that overlap a tiny area  
             # around the point. For layers containing points we have to  
             # choose a larger size of the box we're testing agains so  
             # that we take the size of the markers into account  
             # FIXME: Once the markers are more flexible this part has to  
             # become more flexible too, of course  
             if shapetype == SHAPETYPE_POINT:  
                 box = self.unprojected_rect_around_point(px, py, 5)  
             else:  
                 box = self.unprojected_rect_around_point(px, py, 1)  
             shape_ids = layer.ShapesInRegion(box)  
             shape_ids.reverse()  
   
             if shapetype == SHAPETYPE_POLYGON:  
                 for i in shape_ids:  
                     result = point_in_polygon_shape(layer.shapefile.cobject(),  
                                                     i,  
                                                     filled, stroked,  
                                                     map_proj, layer_proj,  
                                                     scale, -scale, offx, offy,  
                                                     px, py)  
                     if result:  
                         select_shape = i  
                         break  
             elif shapetype == SHAPETYPE_ARC:  
                 for i in shape_ids:  
                     result = point_in_polygon_shape(layer.shapefile.cobject(),  
                                                     i, 0, 1,  
                                                     map_proj, layer_proj,  
                                                     scale, -scale, offx, offy,  
                                                     px, py)  
                     if result < 0:  
                         select_shape = i  
                         break  
             elif shapetype == SHAPETYPE_POINT:  
                 for i in shape_ids:  
                     shape = layer.Shape(i)  
                     x, y = shape.Points()[0]  
                     if inverse:  
                         x, y = inverse(x, y)  
                     if forward:  
                         x, y = forward(x, y)  
                     x = x * scale + offx  
                     y = -y * scale + offy  
                     if hypot(px - x, py - y) < 5:  
                         select_shape = i  
                         break  
   
             if select_shape >= 0:  
                 return layer, select_shape  
         return None, None  
   
     def SelectShapeAt(self, x, y, layer = None):  
         """\  
         Select and return the shape and its layer at window position (x, y)  
   
         If layer is given, only search in that layer. If no layer is  
         given, search through all layers.  
348    
349          Return a tuple (layer, shapeid). If no shape is found, return      def GetTextExtent(self, text):
350          (None, None).          dc = wxClientDC(self)
351          """          font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
352          layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)          dc.SetFont(font)
353          # If layer is None, then shape will also be None. We don't want          return dc.GetTextExtent(text)
         # to deselect the currently selected layer, so we simply select  
         # the already selected layer again.  
         if layer is None:  
             layer = self.interactor.SelectedLayer()  
         self.interactor.SelectLayerAndShape(layer, shape)  
         return result  
354    
355      def LabelShapeAt(self, x, y):      def LabelShapeAt(self, x, y, text=None):
356          """Add or remove a label at window position x, y.          """Add or remove a label at window position x, y.
357    
358          If there's a label at the given position, remove it. Otherwise          If there's a label at the given position, remove it. Otherwise
359          determine the shape at the position, run the label dialog and          determine the shape at the position, run the label dialog and
360          unless the user cancels the dialog, add a laber.          unless the user cancels the dialog, add a label.
361          """          """
         ox = x; oy = y  
362          label_layer = self.map.LabelLayer()          label_layer = self.map.LabelLayer()
363          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
364          if layer is None and shape_index is not None:          if layer is None and shape_index is not None:
365              # a label was selected              ViewPort.LabelShapeAt(self, x, y)
             label_layer.RemoveLabel(shape_index)  
366          elif layer is not None:          elif layer is not None:
367              text = labeldialog.run_label_dialog(self, layer.table, shape_index)              text = labeldialog.run_label_dialog(self,
368              if text:                                                  layer.ShapeStore().Table(),
369                  proj = self.map.projection                                                  shape_index)
370                  if proj is not None:              ViewPort.LabelShapeAt(self, x, y, text)
                     map_proj = proj  
                 else:  
                     map_proj = None  
                 proj = layer.projection  
                 if proj is not None:  
                     layer_proj = proj  
                 else:  
                     layer_proj = None  
   
                 shapetype = layer.ShapeType()  
                 if shapetype == SHAPETYPE_POLYGON:  
                     x, y = shape_centroid(layer.shapefile.cobject(),  
                                           shape_index,  
                                           map_proj, layer_proj, 1, 1, 0, 0)  
                     if map_proj is not None:  
                         x, y = map_proj.Inverse(x, y)  
                 else:  
                     shape = layer.Shape(shape_index)  
                     if shapetype == SHAPETYPE_POINT:  
                         x, y = shape.Points()[0]  
                     else:  
                         # assume SHAPETYPE_ARC  
                         points = shape.Points()  
                         x, y = points[len(points) / 2]  
                     if layer_proj is not None:  
                         x, y = layer_proj.Inverse(x, y)  
                 if shapetype == SHAPETYPE_POINT:  
                     halign = ALIGN_LEFT  
                     valign = ALIGN_CENTER  
                 elif shapetype == SHAPETYPE_POLYGON:  
                     halign = ALIGN_CENTER  
                     valign = ALIGN_CENTER  
                 elif shapetype == SHAPETYPE_ARC:  
                     halign = ALIGN_LEFT  
                     valign = ALIGN_CENTER  
                 label_layer.AddLabel(x, y, text,  
                                      halign = halign, valign = valign)  

Legend:
Removed from v.303  
changed lines
  Added in v.1552

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26