/[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 404 by bh, Fri Feb 14 17:40:26 2003 UTC revision 2068 by bh, Mon Feb 16 19:42:04 2004 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH  # Copyright (c) 2001, 2002, 2003, 2004 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 9  Line 10 
10  Classes for display of a map and interaction with it  Classes for display of a map and interaction with it
11  """  """
12    
13    from __future__ import generators
14    
15  __version__ = "$Revision$"  __version__ = "$Revision$"
16    # $Source$
17    # $Id$
18    
19  from math import hypot  import os.path
20    import time
21    import traceback
22    
23  from wxPython.wx import wxWindow,\  from wxPython.wx import wxWindow, \
24       wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\       wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
25       EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW       EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
26         wxPlatform, wxBeginBusyCursor, wxEndBusyCursor, wxFileDialog, wxSAVE, \
27         wxOVERWRITE_PROMPT, wxID_OK
28    
29    # Export related stuff
30    if wxPlatform == '__WXMSW__':
31        from wxPython.wx import wxMetaFileDC
32    
33  from wxPython import wx  from wxPython import wx
34    
35  from wxproj import point_in_polygon_shape, shape_centroid  from Thuban import _
   
36    
37  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \  from Thuban.Model.messages import MAP_LAYERS_CHANGED, LAYER_CHANGED, \
38       LAYERS_CHANGED, LAYER_LEGEND_CHANGED, LAYER_VISIBILITY_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  
39    
40  from renderer import ScreenRenderer, PrinterRender  from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
41    
42  import labeldialog  import labeldialog
43    
44  from messages import SELECTED_SHAPE, VIEW_POSITION  from viewport import ViewPort, PanTool, output_transform
   
   
 #  
 #   The tools  
 #  
   
 class Tool:  
   
     """  
     Base class for the interactive tools  
     """  
   
     def __init__(self, view):  
         """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  
   
     def draw(self, dc):  
         pass  
   
     def MouseDown(self, event):  
         self.drag_start(event.m_x, event.m_y)  
   
     def MouseMove(self, event):  
         if self.dragging:  
             self.drag_move(event.m_x, event.m_y)  
   
     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"""  
45    
46      def draw(self, dc):  class CanvasPanTool(PanTool):
         sx, sy = self.start  
         cx, cy = self.current  
         dc.DrawRectangle(sx, sy, cx - sx, cy - sy)  
47    
48  class ZoomInTool(RectTool):      """The Canvas Pan Tool"""
   
     """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"  
49    
50      def MouseMove(self, event):      def MouseMove(self, event):
51          if self.dragging:          if self.dragging:
52              Tool.MouseMove(self, event)              PanTool.MouseMove(self, event)
53              sx, sy = self.start              sx, sy = self.start
54              x, y = self.current              x, y = self.current
55              width, height = self.view.GetSizeTuple()              width, height = self.view.GetSizeTuple()
56    
57              bitmapdc = wx.wxMemoryDC()              bitmapdc = wx.wxMemoryDC()
58              bitmapdc.SelectObject(self.view.bitmap)              bitmapdc.SelectObject(self.view.PreviewBitmap())
59    
60              dc = self.view.drag_dc              dc = self.view.drag_dc
61              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
62    
     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)  
   
   
   
   
63  class MapPrintout(wx.wxPrintout):  class MapPrintout(wx.wxPrintout):
64    
65      """      """
66      wxPrintout class for printing Thuban maps      wxPrintout class for printing Thuban maps
67      """      """
68    
69      def __init__(self, map):      def __init__(self, canvas, map, region, selected_layer, selected_shapes):
70          wx.wxPrintout.__init__(self)          wx.wxPrintout.__init__(self)
71            self.canvas = canvas
72          self.map = map          self.map = map
73            self.region = region
74            self.selected_layer = selected_layer
75            self.selected_shapes = selected_shapes
76    
77      def GetPageInfo(self):      def GetPageInfo(self):
78          return (1, 1, 1, 1)          return (1, 1, 1, 1)
# Line 239  class MapPrintout(wx.wxPrintout): Line 86  class MapPrintout(wx.wxPrintout):
86    
87      def draw_on_dc(self, dc):      def draw_on_dc(self, dc):
88          width, height = self.GetPageSizePixels()          width, height = self.GetPageSizePixels()
89          llx, lly, urx, ury = self.map.ProjectedBoundingBox()          scale, offset, mapregion = output_transform(self.canvas.scale,
90          scalex = width / (urx - llx)                                                      self.canvas.offset,
91          scaley = height / (ury - lly)                                                      self.canvas.GetSizeTuple(),
92          scale = min(scalex, scaley)                                                      self.GetPageSizePixels())
         offx = 0.5 * (width - (urx + llx) * scale)  
         offy = 0.5 * (height + (ury + lly) * scale)  
   
93          resx, resy = self.GetPPIPrinter()          resx, resy = self.GetPPIPrinter()
94          renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)          canvas_scale = self.canvas.scale
95          renderer.RenderMap(self.map)          x, y, width, height = self.region
96          return wx.true          renderer = PrinterRenderer(dc, self.map, scale, offset,
97                                       region = (0, 0,
98                                                 (width/canvas_scale)*scale,
99                                                 (height/canvas_scale)*scale),
100                                       resolution = resy,
101                                       destination_region = mapregion)
102            renderer.RenderMap(self.selected_layer, self.selected_shapes)
103            return True
104    
105    
106  class MapCanvas(wxWindow, Publisher):  class MapCanvas(wxWindow, ViewPort):
107    
108      """A widget that displays a map and offers some interaction"""      """A widget that displays a map and offers some interaction"""
109    
110      def __init__(self, parent, winid, interactor):      def __init__(self, parent, winid):
111          wxWindow.__init__(self, parent, winid)          wxWindow.__init__(self, parent, winid)
112          self.SetBackgroundColour(wxColour(255, 255, 255))          ViewPort.__init__(self)
   
         # the map displayed in this canvas. Set with SetMap()  
         self.map = None  
113    
114          # 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  
115    
116          # the bitmap serving as backing store          # the bitmap serving as backing store
117          self.bitmap = None          self.bitmap = None
118            # the monochrome bitmap with the selection if any
119            self.selection_bitmap = None
120    
121            self.backgroundColor = wx.wxWHITE_BRUSH
122    
123          # the interactor          # The rendering iterator object. Used when rendering
124          self.interactor = interactor          # incrementally
125          self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)          self.render_iter = None
   
         # keep track of which layers/shapes are selected to make sure we  
         # only redraw when necessary  
         self.last_selected_layer = None  
         self.last_selected_shape = None  
126    
127          # subscribe the WX events we're interested in          # subscribe the WX events we're interested in
128          EVT_PAINT(self, self.OnPaint)          EVT_PAINT(self, self.OnPaint)
# Line 298  class MapCanvas(wxWindow, Publisher): Line 131  class MapCanvas(wxWindow, Publisher):
131          EVT_MOTION(self, self.OnMotion)          EVT_MOTION(self, self.OnMotion)
132          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
133          wx.EVT_SIZE(self, self.OnSize)          wx.EVT_SIZE(self, self.OnSize)
134            wx.EVT_IDLE(self, self.OnIdle)
135    
136      def __del__(self):      def __del__(self):
137          wxWindow.__del__(self)          wxWindow.__del__(self)
138          Publisher.__del__(self)          ViewPort.__del__(self)
139    
140        def PreviewBitmap(self):
141            return self.bitmap
142    
143        def PanTool(self):
144            """Start the canvas pan tool"""
145            self.SelectTool(CanvasPanTool(self))
146            
147        def SetMap(self, map):
148            redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
149                               LAYER_VISIBILITY_CHANGED)
150            if self.Map() is not None:
151                for channel in redraw_channels:
152                    self.Map().Unsubscribe(channel, self.full_redraw)
153    
154            ViewPort.SetMap(self, map)
155    
156            if self.Map() is not None:
157                for channel in redraw_channels:
158                    self.Map().Subscribe(channel, self.full_redraw)
159    
160            # force a redraw. If map is not empty, it's already been called
161            # by FitMapToWindow but if map is empty it hasn't been called
162            # yet so we have to explicitly call it.
163            self.full_redraw()
164    
165      def OnPaint(self, event):      def OnPaint(self, event):
166          dc = wxPaintDC(self)          dc = wxPaintDC(self)
167          if self.map is not None and self.map.HasLayers():          if self.Map() is not None and self.Map().HasLayers():
168              self.do_redraw()              if self.bitmap is not None:
169                    dc.BeginDrawing()
170                    dc.DrawBitmap(self.bitmap, 0, 0)
171                    if self.selection_bitmap is not None:
172                        dc.DrawBitmap(self.selection_bitmap, 0, 0, True)
173                    dc.EndDrawing()
174          else:          else:
175              # 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
176              # the screen.              # the screen.
# Line 316  class MapCanvas(wxWindow, Publisher): Line 180  class MapCanvas(wxWindow, Publisher):
180              # only thing we may have to do is to call self.Refresh()              # only thing we may have to do is to call self.Refresh()
181              # with a true argument in the right places.              # with a true argument in the right places.
182              dc.BeginDrawing()              dc.BeginDrawing()
183                dc.SetBackground(self.backgroundColor)
184              dc.Clear()              dc.Clear()
185              dc.EndDrawing()              dc.EndDrawing()
186    
187      def do_redraw(self):      def OnIdle(self, event):
188          # This should only be called if we have a non-empty map.          """Idle handler. Redraw the bitmap if necessary"""
189            if (self.bitmap is None
190                or self.render_iter is not None
191                or (self.HasSelectedShapes()
192                    and self.selection_bitmap is None)):
193                event.RequestMore(self._do_redraw())
194    
195        def _do_redraw(self):
196            """Redraw a bit and return whether this method has to be called again.
197    
198            Called by OnIdle to handle the actual redraw. Redraw is
199            incremental for both the bitmap with the normal layers and the
200            bitmap with the selection.
201            """
202            finished = False
203            if self.render_iter is not None:
204                try:
205                    if self.render_iter.next():
206                        # Redraw if the last preview redraw was some time
207                        # ago and the user is not currently dragging the
208                        # mouse because redrawing would interfere with what
209                        # the current tool is drawing on the window.
210                        if not self.dragging \
211                               and time.time() - self.render_last_preview > 0.5:
212                            client_dc = wxClientDC(self)
213                            client_dc.BeginDrawing()
214                            client_dc.DrawBitmap(self.bitmap, 0, 0)
215                            client_dc.EndDrawing()
216                            self.render_last_preview = time.time()
217                    else:
218                        self.render_iter = None
219                        # Redraw if not dragging because redrawing would
220                        # interfere with what the current tool is drawing on
221                        # the window.
222                        if not self.dragging:
223                            self.redraw()
224                        finished = True
225                except StopIteration:
226                    finished = True
227                    self.render_iter = None
228                except:
229                    finished = True
230                    self.render_iter = None
231                    traceback.print_exc()
232            else:
233                self.render_iter = self._render_iterator()
234                self.render_last_preview = time.time()
235            return not finished
236    
237          # Get the window size.      def _render_iterator(self):
238          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
239            dc = wx.wxMemoryDC()
240    
241          # If self.bitmap's still there, reuse it. Otherwise redraw it          render_start = time.time()
242          if self.bitmap is not None:  
243              bitmap = self.bitmap          if self.bitmap is None:
244          else:              self.bitmap = wx.wxEmptyBitmap(width, height)
245              bitmap = wx.wxEmptyBitmap(width, height)              dc.SelectObject(self.bitmap)
             dc = wx.wxMemoryDC()  
             dc.SelectObject(bitmap)  
246              dc.BeginDrawing()              dc.BeginDrawing()
247    
248              # clear the background              dc.SetBackground(self.backgroundColor)
249              dc.SetBrush(wx.wxWHITE_BRUSH)              dc.Clear()
             dc.SetPen(wx.wxTRANSPARENT_PEN)  
             dc.DrawRectangle(0, 0, width, height)  
   
             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  
250    
251              # draw the map into the bitmap              # draw the map into the bitmap
252              renderer = ScreenRenderer(dc, self.scale, self.offset)              renderer = ScreenRenderer(dc, self.Map(), self.scale, self.offset,
253                                          (0, 0, width, height))
254              # Pass the entire bitmap as update region to the renderer.              for cont in renderer.RenderMapIncrementally():
255              # We're redrawing the whole bitmap, after all.                  yield True
             renderer.RenderMap(self.map, (0, 0, width, height),  
                                selected_layer, selected_shape)  
256    
257              dc.EndDrawing()              dc.EndDrawing()
258              dc.SelectObject(wx.wxNullBitmap)              dc.SelectObject(wx.wxNullBitmap)
             self.bitmap = bitmap  
259    
260          # blit the bitmap to the screen          if self.HasSelectedShapes() and self.selection_bitmap is None:
261          dc = wx.wxMemoryDC()              bitmap = wx.wxEmptyBitmap(width, height)
262          dc.SelectObject(bitmap)              dc.SelectObject(bitmap)
263          clientdc = wxClientDC(self)              dc.BeginDrawing()
264          clientdc.BeginDrawing()              dc.SetBackground(wx.wxWHITE_BRUSH)
265          clientdc.Blit(0, 0, width, height, dc, 0, 0)              dc.Clear()
         clientdc.EndDrawing()  
   
     def Print(self):  
         printer = wx.wxPrinter()  
         printout = MapPrintout(self.map)  
         printer.Print(self, printout, wx.true)  
         printout.Destroy()  
   
     def SetMap(self, map):  
         redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,  
                            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()  
266    
267      def Map(self):              renderer = ScreenRenderer(dc, self.Map(), self.scale, self.offset,
268          """Return the map displayed by this canvas"""                                        (0, 0, width, height))
269          return self.map              layer = self.SelectedLayer()
270                shapes = self.selection.SelectedShapes()
271                for cont in renderer.draw_selection_incrementally(layer, shapes):
272                    yield True
273    
274      def redraw(self, *args):              dc.EndDrawing()
275          self.Refresh(0)              dc.SelectObject(wx.wxNullBitmap)
276    
277      def full_redraw(self, *args):              bitmap.SetMask(wx.wxMaskColour(bitmap, wx.wxWHITE))
278          self.bitmap = None              self.selection_bitmap = bitmap
         self.redraw()  
279    
280      def projection_changed(self, *args):          yield False
         self.FitMapToWindow()  
         self.full_redraw()  
281    
282      def set_view_transform(self, scale, offset):      def Export(self):
         self.scale = scale  
         self.offset = offset  
         self.full_redraw()  
283    
284      def proj_to_win(self, x, y):          if hasattr(self, "export_path"):
285          """\              export_path = self.export_path
286          Return the point in  window coords given by projected coordinates x y          else:
287          """              export_path="."
288          offx, offy = self.offset          dlg = wxFileDialog(self, _("Export Map"), export_path, "",
289          return (self.scale * x + offx, -self.scale * y + offy)                             "Enhanced Metafile (*.wmf)|*.wmf",
290                               wxSAVE|wxOVERWRITE_PROMPT)
291      def win_to_proj(self, x, y):          if dlg.ShowModal() == wxID_OK:
292          """\              self.export_path = os.path.dirname(dlg.GetPath())
293          Return the point in projected coordinates given by window coords x y              dc = wxMetaFileDC(dlg.GetPath())
294          """      
295          offx, offy = self.offset              scale, offset, mapregion = output_transform(self.scale,
296          return ((x - offx) / self.scale, (offy - y) / self.scale)                                                          self.offset,
297                                                            self.GetSizeTuple(),
298                                                            dc.GetSizeTuple())
299    
300                selected_layer = self.selection.SelectedLayer()
301                selected_shapes = self.selection.SelectedShapes()
302    
303                width, height = self.GetSizeTuple()
304                renderer = ExportRenderer(dc, self.Map(), scale, offset,
305                                          region = (0, 0,
306                                                    (width/self.scale)*scale,
307                                                    (height/self.scale)*scale),
308                                          destination_region = mapregion)
309                renderer.RenderMap(selected_layer, selected_shapes)
310    
311      def FitRectToWindow(self, rect):              dc.EndDrawing()
312          """Fit the rectangular region given by rect into the window.              dc.Close()
313            dlg.Destroy()
314                    
315          Set scale so that rect (in projected coordinates) just fits into      def Print(self):
316          the window and center it.          printer = wx.wxPrinter()
         """  
317          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
318          llx, lly, urx, ury = rect          selected_layer = self.selection.SelectedLayer()
319          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  
320                    
321          Set the scale so that the map fits exactly into the window and          printout = MapPrintout(self, self.Map(), (0, 0, width, height),
322          center it in the window.                                 selected_layer, selected_shapes)
323          """          printer.Print(self, printout, True)
324          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 SelectTool(self, tool):  
         """Make tool the active tool.  
   
         The parameter should be an instance of Tool or None to indicate  
         that no tool is active.  
         """  
         self.tool = tool  
325    
326      def ZoomInTool(self):      def redraw(self, *args):
327          """Start the zoom in tool"""          self.Refresh(False)
         self.SelectTool(ZoomInTool(self))  
   
     def ZoomOutTool(self):  
         """Start the zoom out tool"""  
         self.SelectTool(ZoomOutTool(self))  
328    
329      def PanTool(self):      def full_redraw(self, *args):
330          """Start the pan tool"""          self.bitmap = None
331          self.SelectTool(PanTool(self))          self.selection_bitmap = None
332            self.render_iter = None
333            self.redraw()
334    
335      def IdentifyTool(self):      def redraw_selection(self, *args):
336          """Start the identify tool"""          self.selection_bitmap = None
337          self.SelectTool(IdentifyTool(self))          self.render_iter = None
338            self.redraw()
     def LabelTool(self):  
         """Start the label tool"""  
         self.SelectTool(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  
339    
340      def CurrentPosition(self):      def map_projection_changed(self, map, old_proj):
341          """Return current position of the mouse in projected coordinates.          ViewPort.map_projection_changed(self, map, old_proj)
342            self.full_redraw()
343    
344          The result is a 2-tuple of floats with the coordinates. If the      def layer_projection_changed(self, *args):
345          mouse is not in the window, the result is None.          ViewPort.layer_projection_changed(self, args)
346          """          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  
347    
348      def set_current_position(self, event):      def set_view_transform(self, scale, offset):
349          """Set the current position from event          ViewPort.set_view_transform(self, scale, offset)
350            self.full_redraw()
351    
352          Should be called by all events that contain mouse positions      def GetPortSizeTuple(self):
353          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)  
354    
355      def OnLeftDown(self, event):      def OnLeftDown(self, event):
356          self.set_current_position(event)          self.MouseLeftDown(event)
357          if self.tool is not None:          if self.tool is not None:
358              self.drag_dc = wxClientDC(self)              self.drag_dc = wxClientDC(self)
359              self.drag_dc.SetLogicalFunction(wxINVERT)              self.drag_dc.SetLogicalFunction(wxINVERT)
360              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
             self.CaptureMouse()  
             self.tool.MouseDown(event)  
361              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
362                self.CaptureMouse()
363              self.dragging = 1              self.dragging = 1
364    
365      def OnLeftUp(self, event):      def OnLeftUp(self, event):
366          self.set_current_position(event)          """Handle EVT_LEFT_UP
367    
368            Release the mouse if it was captured, if a tool is active call
369            its Hide method and call self.MouseLeftUp.
370            """
371            # It's important that ReleaseMouse is called before MouseLeftUp.
372            # MouseLeftUp may pop up modal dialogs which leads to an
373            # effectively frozen X session because the user can only
374            # interact with the dialog but the mouse is still grabbed by the
375            # canvas.
376          if self.dragging:          if self.dragging:
377              self.ReleaseMouse()              if self.HasCapture():
378                    self.ReleaseMouse()
379              try:              try:
380                  self.tool.Hide(self.drag_dc)                  self.tool.Hide(self.drag_dc)
                 self.tool.MouseUp(event)  
381              finally:              finally:
382                  self.drag_dc = None                  self.drag_dc = None
383                  self.dragging = 0                  self.dragging = 0
384            self.MouseLeftUp(event)
385    
386      def OnMotion(self, event):      def OnMotion(self, event):
         self.set_current_position(event)  
387          if self.dragging:          if self.dragging:
388              self.tool.Hide(self.drag_dc)              self.tool.Hide(self.drag_dc)
389              self.tool.MouseMove(event)  
390            self.MouseMove(event)
391    
392            if self.dragging:
393              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
394    
395      def OnLeaveWindow(self, event):      def OnLeaveWindow(self, event):
# Line 596  class MapCanvas(wxWindow, Publisher): Line 405  class MapCanvas(wxWindow, Publisher):
405          self.full_redraw()          self.full_redraw()
406    
407      def shape_selected(self, layer, shape):      def shape_selected(self, layer, shape):
408          """Redraw the map.          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
409            # The selection object takes care that it only issues
410          Receiver for the SELECTED_SHAPE messages. Try to redraw only          # SHAPES_SELECTED messages when the set of selected shapes has
411          when necessary.          # actually changed, so we can do a full redraw of the
412          """          # selection_bitmap unconditionally.
413          # A redraw is necessary when the display has to change, which          ViewPort.shape_selected(self, layer, shape)
414          # means that either the status changes from having no selection          self.redraw_selection()
415          # to having a selection shape or vice versa, or when the fact  
416          # whether there is a selection at all doesn't change, when the      def GetTextExtent(self, text):
417          # shape which is selected has changed (which means that layer or          dc = wxClientDC(self)
418          # shapeid changes).          font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
419          if ((shape is not None or self.last_selected_shape is not None)          dc.SetFont(font)
420              and (shape != self.last_selected_shape          return dc.GetTextExtent(text)
                  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  
421    
422      def unprojected_rect_around_point(self, x, y, dist):      def LabelShapeAt(self, x, y, text=None):
         """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.  
   
         Return a tuple (layer, shapeid). If no shape is found, return  
         (None, None).  
         """  
         layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)  
         # If layer is None, then shape will also be None. We don't want  
         # 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  
   
     def LabelShapeAt(self, x, y):  
423          """Add or remove a label at window position x, y.          """Add or remove a label at window position x, y.
424    
425          If there's a label at the given position, remove it. Otherwise          If there's a label at the given position, remove it. Otherwise
426          determine the shape at the position, run the label dialog and          determine the shape at the position, run the label dialog and
427          unless the user cancels the dialog, add a laber.          unless the user cancels the dialog, add a label.
428          """          """
         ox = x; oy = y  
429          label_layer = self.map.LabelLayer()          label_layer = self.map.LabelLayer()
430          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
431          if layer is None and shape_index is not None:          if layer is None and shape_index is not None:
432              # a label was selected              ViewPort.LabelShapeAt(self, x, y)
             label_layer.RemoveLabel(shape_index)  
433          elif layer is not None:          elif layer is not None:
434              text = labeldialog.run_label_dialog(self, layer.table, shape_index)              text = labeldialog.run_label_dialog(self,
435              if text:                                                  layer.ShapeStore().Table(),
436                  proj = self.map.projection                                                  shape_index)
437                  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.404  
changed lines
  Added in v.2068

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26