/[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 855 by frank, Wed May 7 18:24:27 2003 UTC revision 2599 by russell, Wed Apr 13 16:08:33 2005 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  __version__ = "$Revision$"  from __future__ import generators
14    
15  import sys  __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       wxBITMAP_TYPE_XPM, wxBeginBusyCursor, wxEndBusyCursor, wxCursor, \       wxPlatform, wxBeginBusyCursor, wxEndBusyCursor, wxFileDialog, wxSAVE, \
27       wxImageFromBitmap       EVT_MIDDLE_DOWN, EVT_MIDDLE_UP, \
28         wxOVERWRITE_PROMPT, wxID_OK
29    
30    # Export related stuff
31    if wxPlatform == '__WXMSW__':
32        from wxPython.wx import wxMetaFileDC
33    
34  from wxPython import wx  from wxPython import wx
35    
36  from wxproj import point_in_polygon_shape, shape_centroid  from Thuban import _
   
37    
38  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \  from Thuban.Model.messages import MAP_LAYERS_CHANGED, LAYER_CHANGED, \
39       MAP_LAYERS_CHANGED, LAYER_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  
 from Thuban.Model.color import Color  
40    
41  import resource  from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
   
 from selection import Selection  
 from renderer import ScreenRenderer, PrinterRender  
42    
43  import labeldialog  import labeldialog
44    
45  from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION, \  from viewport import ViewPort, PanTool, output_transform
                      SCALE_CHANGED  
   
   
 #  
 #   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  
46    
47      def draw(self, dc):  class CanvasPanTool(PanTool):
         pass  
48    
49      def MouseDown(self, event):      """The Canvas Pan Tool"""
         self.drag_start(event.m_x, event.m_y)  
50    
51      def MouseMove(self, event):      def MouseMove(self, event):
52          if self.dragging:          if self.dragging:
53              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)  
54              sx, sy = self.start              sx, sy = self.start
55              x, y = self.current              x, y = self.current
56              width, height = self.view.GetSizeTuple()              width, height = self.view.GetSizeTuple()
57    
58              bitmapdc = wx.wxMemoryDC()              bitmapdc = wx.wxMemoryDC()
59              bitmapdc.SelectObject(self.view.bitmap)              bitmapdc.SelectObject(self.view.PreviewBitmap())
60    
61              dc = self.view.drag_dc              dc = self.view.drag_dc
62              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
63    
     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)  
   
   
   
   
64  class MapPrintout(wx.wxPrintout):  class MapPrintout(wx.wxPrintout):
65    
66      """      """
67      wxPrintout class for printing Thuban maps      wxPrintout class for printing Thuban maps
68      """      """
69    
70      def __init__(self, map):      def __init__(self, canvas, map, region, selected_layer, selected_shapes):
71          wx.wxPrintout.__init__(self)          wx.wxPrintout.__init__(self)
72            self.canvas = canvas
73          self.map = map          self.map = map
74            self.region = region
75            self.selected_layer = selected_layer
76            self.selected_shapes = selected_shapes
77    
78      def GetPageInfo(self):      def GetPageInfo(self):
79          return (1, 1, 1, 1)          return (1, 1, 1, 1)
# Line 248  class MapPrintout(wx.wxPrintout): Line 87  class MapPrintout(wx.wxPrintout):
87    
88      def draw_on_dc(self, dc):      def draw_on_dc(self, dc):
89          width, height = self.GetPageSizePixels()          width, height = self.GetPageSizePixels()
90          llx, lly, urx, ury = self.map.ProjectedBoundingBox()          scale, offset, mapregion = output_transform(self.canvas.scale,
91          scalex = width / (urx - llx)                                                      self.canvas.offset,
92          scaley = height / (ury - lly)                                                      self.canvas.GetSizeTuple(),
93          scale = min(scalex, scaley)                                                      self.GetPageSizePixels())
         offx = 0.5 * (width - (urx + llx) * scale)  
         offy = 0.5 * (height + (ury + lly) * scale)  
   
94          resx, resy = self.GetPPIPrinter()          resx, resy = self.GetPPIPrinter()
95          renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)          canvas_scale = self.canvas.scale
96          renderer.RenderMap(self.map)          x, y, width, height = self.region
97          return wx.true          renderer = PrinterRenderer(dc, self.map, scale, offset,
98                                       region = (mapregion[0], mapregion[1],
99                                                 (width/canvas_scale)*scale,
100                                                 (height/canvas_scale)*scale),
101                                       resolution = resy,
102                                       destination_region = mapregion)
103            renderer.RenderMap(self.selected_layer, self.selected_shapes)
104            return True
105    
106    
107  class MapCanvas(wxWindow, Publisher):  class MapCanvas(wxWindow, ViewPort):
108    
109      """A widget that displays a map and offers some interaction"""      """A widget that displays a map and offers some interaction"""
110    
     # Some messages that can be subscribed/unsubscribed directly through  
     # the MapCanvas come in fact from other objects. This is a dict  
     # mapping those messages to the names of the instance variables they  
     # actually come from. The delegation is implemented in the Subscribe  
     # and Unsubscribe methods  
     delegated_messages = {LAYER_SELECTED: "selection",  
                           SHAPES_SELECTED: "selection"}  
   
     # Methods delegated to some instance variables. The delegation is  
     # implemented in the __getattr__ method.  
     delegated_methods = {"SelectLayer": "selection",  
                          "SelectShapes": "selection",  
                          "SelectedLayer": "selection",  
                          "HasSelectedLayer": "selection",  
                          "HasSelectedShapes": "selection"}  
   
111      def __init__(self, parent, winid):      def __init__(self, parent, winid):
112          wxWindow.__init__(self, parent, winid)          wxWindow.__init__(self, parent, winid)
113          self.SetBackgroundColour(wxColour(255, 255, 255))          ViewPort.__init__(self)
   
         # the map displayed in this canvas. Set with SetMap()  
         self.map = None  
114    
115          # 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  
116    
117          # the bitmap serving as backing store          # the bitmap serving as backing store
118          self.bitmap = None          self.bitmap = None
119            # the monochrome bitmap with the selection if any
120            self.selection_bitmap = None
121    
122            self.backgroundColor = wx.wxWHITE_BRUSH
123    
124          # the selection          # The rendering iterator object. Used when rendering
125          self.selection = Selection()          # incrementally
126          self.selection.Subscribe(SHAPES_SELECTED , 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  
127    
128          # subscribe the WX events we're interested in          # subscribe the WX events we're interested in
129          EVT_PAINT(self, self.OnPaint)          EVT_PAINT(self, self.OnPaint)
130          EVT_LEFT_DOWN(self, self.OnLeftDown)          EVT_LEFT_DOWN(self, self.OnLeftDown)
131          EVT_LEFT_UP(self, self.OnLeftUp)          EVT_LEFT_UP(self, self.OnLeftUp)
132            EVT_MIDDLE_DOWN(self, self.OnMiddleDown)
133            EVT_MIDDLE_UP(self, self.OnMiddleUp)
134          EVT_MOTION(self, self.OnMotion)          EVT_MOTION(self, self.OnMotion)
135          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
136          wx.EVT_SIZE(self, self.OnSize)          wx.EVT_SIZE(self, self.OnSize)
137            wx.EVT_IDLE(self, self.OnIdle)
138    
139      def __del__(self):      def __del__(self):
140          wxWindow.__del__(self)          wxWindow.__del__(self)
141          Publisher.__del__(self)          ViewPort.__del__(self)
142    
143      def Subscribe(self, channel, *args):      def PreviewBitmap(self):
144          """Extend the inherited method to handle delegated messages.          return self.bitmap
145    
146          If channel is one of the delegated messages call the appropriate      def PanTool(self):
147          object's Subscribe method. Otherwise just call the inherited          """Start the canvas pan tool"""
148          method.          self.SelectTool(CanvasPanTool(self))
149          """          
150          if channel in self.delegated_messages:      def SetMap(self, map):
151              object = getattr(self, self.delegated_messages[channel])          redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
152              object.Subscribe(channel, *args)                             LAYER_VISIBILITY_CHANGED)
153          else:          if self.Map() is not None:
154              Publisher.Subscribe(self, channel, *args)              for channel in redraw_channels:
155                    self.Map().Unsubscribe(channel, self.full_redraw)
156    
157      def Unsubscribe(self, channel, *args):          ViewPort.SetMap(self, map)
         """Extend the inherited method to handle delegated messages.  
158    
159          If channel is one of the delegated messages call the appropriate          if self.Map() is not None:
160          object's Unsubscribe method. Otherwise just call the inherited              for channel in redraw_channels:
161          method.                  self.Map().Subscribe(channel, self.full_redraw)
         """  
         if channel in self.delegated_messages:  
             object = getattr(self, self.delegated_messages[channel])  
             object.Unsubscribe(channel, *args)  
         else:  
             Publisher.Unsubscribe(self, channel, *args)  
162    
163      def __getattr__(self, attr):          # force a redraw. If map is not empty, it's already been called
164          if attr in self.delegated_methods:          # by FitMapToWindow but if map is empty it hasn't been called
165              return getattr(getattr(self, self.delegated_methods[attr]), attr)          # yet so we have to explicitly call it.
166          raise AttributeError(attr)          self.full_redraw()
167    
168      def OnPaint(self, event):      def OnPaint(self, event):
169          dc = wxPaintDC(self)          dc = wxPaintDC(self)
170          clear = self.map is None or not self.map.HasLayers()          if self.Map() is not None and self.Map().HasLayers():
171                if self.bitmap is not None:
172          #wxBeginBusyCursor()                  dc.BeginDrawing()
173                    dc.DrawBitmap(self.bitmap, 0, 0)
174          if not clear:                  if self.selection_bitmap is not None:
175              try:                      dc.DrawBitmap(self.selection_bitmap, 0, 0, True)
176                  self.do_redraw()                  dc.EndDrawing()
177              except:          else:
                 print "Error during drawing:", sys.exc_info()[0]  
                 clear = True  
   
         if clear:  
178              # 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
179              # the screen.              # the screen.
180    
# Line 381  class MapCanvas(wxWindow, Publisher): Line 183  class MapCanvas(wxWindow, Publisher):
183              # only thing we may have to do is to call self.Refresh()              # only thing we may have to do is to call self.Refresh()
184              # with a true argument in the right places.              # with a true argument in the right places.
185              dc.BeginDrawing()              dc.BeginDrawing()
186                dc.SetBackground(self.backgroundColor)
187              dc.Clear()              dc.Clear()
188              dc.EndDrawing()              dc.EndDrawing()
189    
190          #wxEndBusyCursor()      def OnIdle(self, event):
191            """Idle handler. Redraw the bitmap if necessary"""
192      def do_redraw(self):          if (self.Map() is not None
193          # This should only be called if we have a non-empty map.              and (self.bitmap is None
194                     or self.render_iter is not None
195                     or (self.HasSelectedShapes()
196                         and self.selection_bitmap is None))):
197                event.RequestMore(self._do_redraw())
198    
199        def _do_redraw(self):
200            """Redraw a bit and return whether this method has to be called again.
201    
202            Called by OnIdle to handle the actual redraw. Redraw is
203            incremental for both the bitmap with the normal layers and the
204            bitmap with the selection.
205            """
206            finished = False
207            if self.render_iter is not None:
208                try:
209                    if self.render_iter.next():
210                        # Redraw if the last preview redraw was some time
211                        # ago and the user is not currently dragging the
212                        # mouse because redrawing would interfere with what
213                        # the current tool is drawing on the window.
214                        if not self.dragging \
215                               and time.time() - self.render_last_preview > 0.5:
216                            client_dc = wxClientDC(self)
217                            client_dc.BeginDrawing()
218                            client_dc.DrawBitmap(self.bitmap, 0, 0)
219                            client_dc.EndDrawing()
220                            self.render_last_preview = time.time()
221                    else:
222                        self.render_iter = None
223                        # Redraw if not dragging because redrawing would
224                        # interfere with what the current tool is drawing on
225                        # the window.
226                        if not self.dragging:
227                            self.redraw()
228                        finished = True
229                except StopIteration:
230                    finished = True
231                    self.render_iter = None
232                except:
233                    finished = True
234                    self.render_iter = None
235                    traceback.print_exc()
236            else:
237                self.render_iter = self._render_iterator()
238                self.render_last_preview = time.time()
239            return not finished
240    
241          # Get the window size.      def _render_iterator(self):
242          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
243            dc = wx.wxMemoryDC()
244    
245          # If self.bitmap's still there, reuse it. Otherwise redraw it          render_start = time.time()
246          if self.bitmap is not None:  
247              bitmap = self.bitmap          if self.bitmap is None:
248          else:              self.bitmap = wx.wxEmptyBitmap(width, height)
249              bitmap = wx.wxEmptyBitmap(width, height)              dc.SelectObject(self.bitmap)
             dc = wx.wxMemoryDC()  
             dc.SelectObject(bitmap)  
250              dc.BeginDrawing()              dc.BeginDrawing()
251    
252              # clear the background              dc.SetBackground(self.backgroundColor)
             #dc.SetBrush(wx.wxWHITE_BRUSH)  
             #dc.SetPen(wx.wxTRANSPARENT_PEN)  
             #dc.DrawRectangle(0, 0, width, height)  
             dc.SetBackground(wx.wxWHITE_BRUSH)  
253              dc.Clear()              dc.Clear()
254    
             selected_layer = self.selection.SelectedLayer()  
             selected_shapes = self.selection.SelectedShapes()  
   
255              # draw the map into the bitmap              # draw the map into the bitmap
256              renderer = ScreenRenderer(dc, self.scale, self.offset)              renderer = ScreenRenderer(dc, self.Map(), self.scale, self.offset,
257                                          (0, 0, width, height))
258              # Pass the entire bitmap as update region to the renderer.              for cont in renderer.RenderMapIncrementally():
259              # We're redrawing the whole bitmap, after all.                  yield True
             renderer.RenderMap(self.map, (0, 0, width, height),  
                                selected_layer, selected_shapes)  
260    
261              dc.EndDrawing()              dc.EndDrawing()
262              dc.SelectObject(wx.wxNullBitmap)              dc.SelectObject(wx.wxNullBitmap)
             self.bitmap = bitmap  
263    
264          # blit the bitmap to the screen          if self.HasSelectedShapes() and self.selection_bitmap is None:
265          dc = wx.wxMemoryDC()              bitmap = wx.wxEmptyBitmap(width, height)
266          dc.SelectObject(bitmap)              dc.SelectObject(bitmap)
267          clientdc = wxClientDC(self)              dc.BeginDrawing()
268          clientdc.BeginDrawing()              dc.SetBackground(wx.wxWHITE_BRUSH)
269          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 = (MAP_LAYERS_CHANGED, LAYER_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  
         self.selection.ClearSelection()  
         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()  
   
     def Map(self):  
         """Return the map displayed by this canvas"""  
         return self.map  
   
     def redraw(self, *args):  
         self.Refresh(0)  
   
     def full_redraw(self, *args):  
         self.bitmap = None  
         self.redraw()  
   
     def projection_changed(self, *args):  
         self.FitMapToWindow()  
         self.full_redraw()  
270    
271      def set_view_transform(self, scale, offset):              renderer = ScreenRenderer(dc, self.Map(), self.scale, self.offset,
272          self.scale = scale                                        (0, 0, width, height))
273          self.offset = offset              layer = self.SelectedLayer()
274          self.full_redraw()              shapes = self.selection.SelectedShapes()
275          self.issue(SCALE_CHANGED, scale)              for cont in renderer.draw_selection_incrementally(layer, shapes):
276                    yield True
277    
278      def proj_to_win(self, x, y):              dc.EndDrawing()
279          """\              dc.SelectObject(wx.wxNullBitmap)
         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)  
280    
281      def win_to_proj(self, x, y):              bitmap.SetMask(wx.wxMaskColour(bitmap, wx.wxWHITE))
282          """\              self.selection_bitmap = bitmap
         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)  
283    
284      def FitRectToWindow(self, rect):          yield False
         """Fit the rectangular region given by rect into the window.  
285    
286          Set scale so that rect (in projected coordinates) just fits into      def Export(self):
         the window and center it.  
         """  
         width, height = self.GetSizeTuple()  
         llx, lly, urx, ury = rect  
         if llx == urx or lly == ury:  
             # zero width 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))  
287    
288      def FitMapToWindow(self):          if hasattr(self, "export_path"):
289          """Fit the map to the window              export_path = self.export_path
290            else:
291                export_path="."
292            dlg = wxFileDialog(self, _("Export Map"), export_path, "",
293                               "Enhanced Metafile (*.wmf)|*.wmf",
294                               wxSAVE|wxOVERWRITE_PROMPT)
295            if dlg.ShowModal() == wxID_OK:
296                self.export_path = os.path.dirname(dlg.GetPath())
297                dc = wxMetaFileDC(dlg.GetPath())
298        
299                scale, offset, mapregion = output_transform(self.scale,
300                                                            self.offset,
301                                                            self.GetSizeTuple(),
302                                                            dc.GetSizeTuple())
303    
304          Set the scale so that the map fits exactly into the window and              selected_layer = self.selection.SelectedLayer()
305          center it in the window.              selected_shapes = self.selection.SelectedShapes()
         """  
         bbox = self.map.ProjectedBoundingBox()  
         if bbox is not None:  
             self.FitRectToWindow(bbox)  
306    
307      def FitLayerToWindow(self, layer):              width, height = self.GetSizeTuple()
308          """Fit the given layer to the window.              renderer = ExportRenderer(dc, self.Map(), scale, offset,
309                                          region = (0, 0,
310                                                    (width/self.scale)*scale,
311                                                    (height/self.scale)*scale),
312                                          destination_region = mapregion)
313                renderer.RenderMap(selected_layer, selected_shapes)
314    
315          Set the scale so that the layer fits exactly into the window and              dc.EndDrawing()
316          center it in the window.              dc.Close()
317          """          dlg.Destroy()
318                    
319          bbox = layer.LatLongBoundingBox()      def Print(self):
320          if bbox is not None:          printer = wx.wxPrinter()
             proj = self.map.GetProjection()  
             if proj is not None:  
                 bbox = proj.ForwardBBox(bbox)  
   
             if bbox is not None:  
                 self.FitRectToWindow(bbox)  
   
     def FitSelectedToWindow(self):  
         layer = self.selection.SelectedLayer()  
         shapes = self.selection.SelectedShapes()  
   
         bbox = layer.ShapesBoundingBox(shapes)  
         if bbox is not None:  
             proj = self.map.GetProjection()  
             if proj is not None:  
                 bbox = proj.ForwardBBox(bbox)  
   
             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  
321          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
322          llx, lly = self.win_to_proj(0, height - 1)          selected_layer = self.selection.SelectedLayer()
323          urx, ury = self.win_to_proj(width - 1, 0)          selected_shapes = self.selection.SelectedShapes()
324            
325          sx, sy, ex, ey = rect          printout = MapPrintout(self, self.Map(), (0, 0, width, height),
326          scalex = (ex - sx) / (urx - llx)                                 selected_layer, selected_shapes)
327          scaley = (ey - sy) / (ury - lly)          printer.Print(self, printout, True)
328          scale = min(scalex, scaley)          printout.Destroy()
   
         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))  
329    
330      def SelectTool(self, tool):      def redraw(self, *args):
331          """Make tool the active tool.          self.Refresh(False)
332    
333          The parameter should be an instance of Tool or None to indicate      def full_redraw(self, *args):
334          that no tool is active.          self.bitmap = None
335          """          self.selection_bitmap = None
336          self.tool = tool          self.render_iter = None
337            self.redraw()
338    
339      def ZoomInTool(self):      def redraw_selection(self, *args):
340          """Start the zoom in tool"""          self.selection_bitmap = None
341          self.SelectTool(ZoomInTool(self))          self.render_iter = None
342            self.redraw()
     def ZoomOutTool(self):  
         """Start the zoom out tool"""  
         self.SelectTool(ZoomOutTool(self))  
343    
344      def PanTool(self):      def map_projection_changed(self, map, old_proj):
345          """Start the pan tool"""          ViewPort.map_projection_changed(self, map, old_proj)
346          self.SelectTool(PanTool(self))          self.full_redraw()
         #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)  
         #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)  
         #print bmp  
         #img = wxImageFromBitmap(bmp)  
         #print img  
         #cur = wxCursor(img)  
         #print cur  
         #self.SetCursor(cur)  
   
     def IdentifyTool(self):  
         """Start the identify tool"""  
         self.SelectTool(IdentifyTool(self))  
   
     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  
347    
348      def CurrentPosition(self):      def layer_projection_changed(self, *args):
349          """Return current position of the mouse in projected coordinates.          ViewPort.layer_projection_changed(self, args)
350            self.full_redraw()
351    
352          The result is a 2-tuple of floats with the coordinates. If the      def set_view_transform(self, scale, offset):
353          mouse is not in the window, the result is None.          ViewPort.set_view_transform(self, scale, offset)
354          """          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  
355    
356      def set_current_position(self, event):      def GetPortSizeTuple(self):
357          """Set the current position from event          return self.GetSizeTuple()
358    
359          Should be called by all events that contain mouse positions      def OnMiddleDown(self, event):
360          especially EVT_MOTION. The event paramete may be None to          self.remembertool = self.tool
361          indicate the the pointer left the window.          if self.Map() is not None and self.Map().HasLayers():
362          """              self.PanTool()
363          if event is not None:              self.OnLeftDown(event)
364              self.current_position = (event.m_x, event.m_y)  
365          else:      def OnMiddleUp(self, event):
366              self.current_position = None          self.OnLeftUp(event)
367          self.issue(VIEW_POSITION)          if self.remembertool:
368                self.SelectTool(self.remembertool)
369    
370      def OnLeftDown(self, event):      def OnLeftDown(self, event):
371          self.set_current_position(event)          self.MouseLeftDown(event)
372          if self.tool is not None:          if self.tool is not None:
373              self.drag_dc = wxClientDC(self)              self.drag_dc = wxClientDC(self)
374              self.drag_dc.SetLogicalFunction(wxINVERT)              self.drag_dc.SetLogicalFunction(wxINVERT)
375              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
             self.CaptureMouse()  
             self.tool.MouseDown(event)  
376              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
377                self.CaptureMouse()
378              self.dragging = 1              self.dragging = 1
379    
380      def OnLeftUp(self, event):      def OnLeftUp(self, event):
381          self.set_current_position(event)          """Handle EVT_LEFT_UP
382    
383            Release the mouse if it was captured, if a tool is active call
384            its Hide method and call self.MouseLeftUp.
385            """
386            # It's important that ReleaseMouse is called before MouseLeftUp.
387            # MouseLeftUp may pop up modal dialogs which leads to an
388            # effectively frozen X session because the user can only
389            # interact with the dialog but the mouse is still grabbed by the
390            # canvas.
391          if self.dragging:          if self.dragging:
392              self.ReleaseMouse()              if self.HasCapture():
393                    self.ReleaseMouse()
394              try:              try:
395                  self.tool.Hide(self.drag_dc)                  self.tool.Hide(self.drag_dc)
                 self.tool.MouseUp(event)  
396              finally:              finally:
397                  self.drag_dc = None                  self.drag_dc = None
398                  self.dragging = 0                  self.dragging = 0
399            self.MouseLeftUp(event)
400    
401      def OnMotion(self, event):      def OnMotion(self, event):
         self.set_current_position(event)  
402          if self.dragging:          if self.dragging:
403              self.tool.Hide(self.drag_dc)              self.tool.Hide(self.drag_dc)
404              self.tool.MouseMove(event)  
405            self.MouseMove(event)
406    
407            if self.dragging:
408              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
409    
410      def OnLeaveWindow(self, event):      def OnLeaveWindow(self, event):
# Line 698  class MapCanvas(wxWindow, Publisher): Line 418  class MapCanvas(wxWindow, Publisher):
418          # Even when the window becomes larger some parts of the bitmap          # Even when the window becomes larger some parts of the bitmap
419          # could be reused.          # could be reused.
420          self.full_redraw()          self.full_redraw()
         pass  
421    
422      def shape_selected(self, layer, shape):      def shape_selected(self, layer, shape):
423          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
424          # The selection object takes care that it only issues          # The selection object takes care that it only issues
425          # SHAPES_SELECTED messages when the set of selected shapes has          # SHAPES_SELECTED messages when the set of selected shapes has
426          # actually changed, so we can do a full redraw unconditionally.          # actually changed, so we can do a full redraw of the
427          # FIXME: We should perhaps try to limit the redraw to the are          # selection_bitmap unconditionally.
428          # actually covered by the shapes before and after the selection          ViewPort.shape_selected(self, layer, shape)
429          # change.          self.redraw_selection()
430          self.full_redraw()  
431        def GetTextExtent(self, text):
432      def unprojected_rect_around_point(self, x, y, dist):          dc = wxClientDC(self)
433          """return a rect dist pixels around (x, y) in unprojected corrdinates          font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
434            dc.SetFont(font)
435          The return value is a tuple (minx, miny, maxx, maxy) suitable a          return dc.GetTextExtent(text)
         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  
436    
437          if searched_layer:      def LabelShapeAt(self, x, y, text=None):
             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.GetClassification().GetDefaultFill() \  
                      is not Color.Transparent  
             stroked = layer.GetClassification().GetDefaultLineColor() \  
                       is not Color.Transparent  
   
             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.selection.SelectedLayer()  
             shapes = []  
         else:  
             shapes = [shape]  
         self.selection.SelectShapes(layer, shapes)  
         return result  
   
     def LabelShapeAt(self, x, y):  
438          """Add or remove a label at window position x, y.          """Add or remove a label at window position x, y.
439    
440          If there's a label at the given position, remove it. Otherwise          If there's a label at the given position, remove it. Otherwise
441          determine the shape at the position, run the label dialog and          determine the shape at the position, run the label dialog and
442          unless the user cancels the dialog, add a laber.          unless the user cancels the dialog, add a label.
443          """          """
         ox = x; oy = y  
444          label_layer = self.map.LabelLayer()          label_layer = self.map.LabelLayer()
445          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
446          if layer is None and shape_index is not None:          if layer is None and shape_index is not None:
447              # a label was selected              ViewPort.LabelShapeAt(self, x, y)
             label_layer.RemoveLabel(shape_index)  
448          elif layer is not None:          elif layer is not None:
449              text = labeldialog.run_label_dialog(self, layer.table, shape_index)              text = labeldialog.run_label_dialog(self,
450              if text:                                                  layer.ShapeStore().Table(),
451                  proj = self.map.projection                                                  shape_index)
452                  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.855  
changed lines
  Added in v.2599

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26