/[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 926 by frank, Mon May 19 12:09:01 2003 UTC revision 2454 by bh, Mon Dec 13 18:26:11 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]>  # Frank Koormann <[email protected]>
# Line 10  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  from Thuban import _  __version__ = "$Revision$"
16    # $Source$
17    # $Id$
18    
 import sys  
19  import os.path  import os.path
20    import time
21    import traceback
22    
23  from math import hypot  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, wxPlatform       wxOVERWRITE_PROMPT, wxID_OK
28    
29  # Export related stuff  # Export related stuff
30  if wxPlatform == '__WXMSW__':  if wxPlatform == '__WXMSW__':
31      from wxPython.wx import wxMetaFileDC      from wxPython.wx import wxMetaFileDC
 from wxPython.wx import wxFileDialog, wxSAVE, wxOVERWRITE_PROMPT, wxID_OK  
32    
33  from wxPython import wx  from wxPython import wx
34    
35  from wxproj import point_in_polygon_shape, shape_centroid  from Thuban import _
   
   
 from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \  
      MAP_LAYERS_CHANGED, LAYER_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  
36    
37  import resource  from Thuban.Model.messages import MAP_LAYERS_CHANGED, LAYER_CHANGED, \
38         LAYER_VISIBILITY_CHANGED
39    
 from selection import Selection  
40  from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer  from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
41    
42  import labeldialog  import labeldialog
43    
44  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  
     """  
45    
46      def __init__(self, view):  class CanvasPanTool(PanTool):
         """Intitialize the tool. The view is the canvas displaying the map"""  
         self.view = view  
         self.start = self.current = None  
         self.dragging = 0  
         self.drawn = 0  
   
     def Name(self):  
         """Return the tool's name"""  
         return ''  
   
     def drag_start(self, x, y):  
         self.start = self.current = x, y  
         self.dragging = 1  
   
     def drag_move(self, x, y):  
         self.current = x, y  
   
     def drag_stop(self, x, y):  
         self.current = x, y  
         self.dragging = 0  
   
     def Show(self, dc):  
         if not self.drawn:  
             self.draw(dc)  
         self.drawn = 1  
   
     def Hide(self, dc):  
         if self.drawn:  
             self.draw(dc)  
         self.drawn = 0  
47    
48      def draw(self, dc):      """The Canvas Pan Tool"""
         pass  
   
     def MouseDown(self, event):  
         self.drag_start(event.m_x, event.m_y)  
49    
50      def MouseMove(self, event):      def MouseMove(self, event):
51          if self.dragging:          if self.dragging:
52              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)  
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      """      """
# Line 258  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          scale, offset, mapregion = OutputTransform(self.canvas.scale,          scale, offset, mapregion = output_transform(self.canvas.scale,
90                                                     self.canvas.offset,                                                      self.canvas.offset,
91                                                     self.canvas.GetSizeTuple(),                                                      self.canvas.GetSizeTuple(),
92                                                     self.GetPageSizePixels())                                                      self.GetPageSizePixels())
93          resx, resy = self.GetPPIPrinter()          resx, resy = self.GetPPIPrinter()
         renderer = PrinterRenderer(dc, scale, offset, resolution = resy)  
         x, y, width, height = self.region  
94          canvas_scale = self.canvas.scale          canvas_scale = self.canvas.scale
95          renderer.RenderMap(self.map,          x, y, width, height = self.region
96                             (0,0,          renderer = PrinterRenderer(dc, self.map, scale, offset,
97                                  (width/canvas_scale)*scale,                                     region = (mapregion[0], mapregion[1],
98                                  (height/canvas_scale)*scale),                                               (width/canvas_scale)*scale,
99                                  mapregion,                                               (height/canvas_scale)*scale),
100                             self.selected_layer, self.selected_shapes)                                     resolution = resy,
101          return wx.true                                     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    
     # 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",  
                          "SelectedShapes": "selection"}  
   
110      def __init__(self, parent, winid):      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)
113    
114          # the map displayed in this canvas. Set with SetMap()          self.SetBackgroundColour(wxColour(255, 255, 255))
         self.map = None  
   
         # scale and offset describe the transformation from projected  
         # 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 selection          # The rendering iterator object. Used when rendering
124          self.selection = Selection()          # incrementally
125          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  
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 338  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 Subscribe(self, channel, *args):      def PreviewBitmap(self):
141          """Extend the inherited method to handle delegated messages.          return self.bitmap
142    
143          If channel is one of the delegated messages call the appropriate      def PanTool(self):
144          object's Subscribe method. Otherwise just call the inherited          """Start the canvas pan tool"""
145          method.          self.SelectTool(CanvasPanTool(self))
146          """          
147          if channel in self.delegated_messages:      def SetMap(self, map):
148              object = getattr(self, self.delegated_messages[channel])          redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
149              object.Subscribe(channel, *args)                             LAYER_VISIBILITY_CHANGED)
150          else:          if self.Map() is not None:
151              Publisher.Subscribe(self, channel, *args)              for channel in redraw_channels:
152                    self.Map().Unsubscribe(channel, self.full_redraw)
153    
154      def Unsubscribe(self, channel, *args):          ViewPort.SetMap(self, map)
         """Extend the inherited method to handle delegated messages.  
155    
156          If channel is one of the delegated messages call the appropriate          if self.Map() is not None:
157          object's Unsubscribe method. Otherwise just call the inherited              for channel in redraw_channels:
158          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)  
159    
160      def __getattr__(self, attr):          # force a redraw. If map is not empty, it's already been called
161          if attr in self.delegated_methods:          # by FitMapToWindow but if map is empty it hasn't been called
162              return getattr(getattr(self, self.delegated_methods[attr]), attr)          # yet so we have to explicitly call it.
163          raise AttributeError(attr)          self.full_redraw()
164    
165      def OnPaint(self, event):      def OnPaint(self, event):
166          dc = wxPaintDC(self)          dc = wxPaintDC(self)
167          clear = self.map is None or not self.map.HasLayers()          if self.Map() is not None and self.Map().HasLayers():
168                if self.bitmap is not None:
169          #wxBeginBusyCursor()                  dc.BeginDrawing()
170                    dc.DrawBitmap(self.bitmap, 0, 0)
171          if not clear:                  if self.selection_bitmap is not None:
172              try:                      dc.DrawBitmap(self.selection_bitmap, 0, 0, True)
173                  self.do_redraw()                  dc.EndDrawing()
174              except:          else:
                 print "Error during drawing:", sys.exc_info()[0]  
                 clear = True  
   
         if clear:  
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.
177    
# Line 396  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          #wxEndBusyCursor()      def OnIdle(self, event):
188            """Idle handler. Redraw the bitmap if necessary"""
189      def do_redraw(self):          if (self.Map() is not None
190          # This should only be called if we have a non-empty map.              and (self.bitmap is None
191                     or self.render_iter is not None
192                     or (self.HasSelectedShapes()
193                         and self.selection_bitmap is None))):
194                event.RequestMore(self._do_redraw())
195    
196        def _do_redraw(self):
197            """Redraw a bit and return whether this method has to be called again.
198    
199            Called by OnIdle to handle the actual redraw. Redraw is
200            incremental for both the bitmap with the normal layers and the
201            bitmap with the selection.
202            """
203            finished = False
204            if self.render_iter is not None:
205                try:
206                    if self.render_iter.next():
207                        # Redraw if the last preview redraw was some time
208                        # ago and the user is not currently dragging the
209                        # mouse because redrawing would interfere with what
210                        # the current tool is drawing on the window.
211                        if not self.dragging \
212                               and time.time() - self.render_last_preview > 0.5:
213                            client_dc = wxClientDC(self)
214                            client_dc.BeginDrawing()
215                            client_dc.DrawBitmap(self.bitmap, 0, 0)
216                            client_dc.EndDrawing()
217                            self.render_last_preview = time.time()
218                    else:
219                        self.render_iter = None
220                        # Redraw if not dragging because redrawing would
221                        # interfere with what the current tool is drawing on
222                        # the window.
223                        if not self.dragging:
224                            self.redraw()
225                        finished = True
226                except StopIteration:
227                    finished = True
228                    self.render_iter = None
229                except:
230                    finished = True
231                    self.render_iter = None
232                    traceback.print_exc()
233            else:
234                self.render_iter = self._render_iterator()
235                self.render_last_preview = time.time()
236            return not finished
237    
238          # Get the window size.      def _render_iterator(self):
239          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
240            dc = wx.wxMemoryDC()
241    
242          # If self.bitmap's still there, reuse it. Otherwise redraw it          render_start = time.time()
243          if self.bitmap is not None:  
244              bitmap = self.bitmap          if self.bitmap is None:
245          else:              self.bitmap = wx.wxEmptyBitmap(width, height)
246              bitmap = wx.wxEmptyBitmap(width, height)              dc.SelectObject(self.bitmap)
             dc = wx.wxMemoryDC()  
             dc.SelectObject(bitmap)  
247              dc.BeginDrawing()              dc.BeginDrawing()
248    
249              # 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)  
250              dc.Clear()              dc.Clear()
251    
             selected_layer = self.selection.SelectedLayer()  
             selected_shapes = self.selection.SelectedShapes()  
   
252              # draw the map into the bitmap              # draw the map into the bitmap
253              renderer = ScreenRenderer(dc, self.scale, self.offset)              renderer = ScreenRenderer(dc, self.Map(), self.scale, self.offset,
254                                          (0, 0, width, height))
255                for cont in renderer.RenderMapIncrementally():
256                    yield True
257    
258              # Pass the entire bitmap as update region to the renderer.              dc.EndDrawing()
259              # We're redrawing the whole bitmap, after all.              dc.SelectObject(wx.wxNullBitmap)
260              renderer.RenderMap(self.map, (0, 0, width, height),  
261                                 selected_layer, selected_shapes)          if self.HasSelectedShapes() and self.selection_bitmap is None:
262                bitmap = wx.wxEmptyBitmap(width, height)
263                dc.SelectObject(bitmap)
264                dc.BeginDrawing()
265                dc.SetBackground(wx.wxWHITE_BRUSH)
266                dc.Clear()
267    
268                renderer = ScreenRenderer(dc, self.Map(), self.scale, self.offset,
269                                          (0, 0, width, height))
270                layer = self.SelectedLayer()
271                shapes = self.selection.SelectedShapes()
272                for cont in renderer.draw_selection_incrementally(layer, shapes):
273                    yield True
274    
275              dc.EndDrawing()              dc.EndDrawing()
276              dc.SelectObject(wx.wxNullBitmap)              dc.SelectObject(wx.wxNullBitmap)
             self.bitmap = bitmap  
277    
278          # blit the bitmap to the screen              bitmap.SetMask(wx.wxMaskColour(bitmap, wx.wxWHITE))
279          dc = wx.wxMemoryDC()              self.selection_bitmap = bitmap
280          dc.SelectObject(bitmap)  
281          clientdc = wxClientDC(self)          yield False
         clientdc.BeginDrawing()  
         clientdc.Blit(0, 0, width, height, dc, 0, 0)  
         clientdc.EndDrawing()  
282    
283      def Export(self):      def Export(self):
284    
285          if hasattr(self, "export_path"):          if hasattr(self, "export_path"):
286              export_path = self.export_path              export_path = self.export_path
287          else:          else:
# Line 458  class MapCanvas(wxWindow, Publisher): Line 293  class MapCanvas(wxWindow, Publisher):
293              self.export_path = os.path.dirname(dlg.GetPath())              self.export_path = os.path.dirname(dlg.GetPath())
294              dc = wxMetaFileDC(dlg.GetPath())              dc = wxMetaFileDC(dlg.GetPath())
295            
296              scale, offset, mapregion = OutputTransform(self.scale,              scale, offset, mapregion = output_transform(self.scale,
297                                                         self.offset,                                                          self.offset,
298                                                         self.GetSizeTuple(),                                                          self.GetSizeTuple(),
299                                                         dc.GetSizeTuple())                                                          dc.GetSizeTuple())
300    
301              selected_layer = self.selection.SelectedLayer()              selected_layer = self.selection.SelectedLayer()
302              selected_shapes = self.selection.SelectedShapes()              selected_shapes = self.selection.SelectedShapes()
303    
             renderer = ExportRenderer(dc, scale, offset)  
   
             # Pass the entire bitmap as update region to the renderer.  
             # We're redrawing the whole bitmap, after all.  
304              width, height = self.GetSizeTuple()              width, height = self.GetSizeTuple()
305              renderer.RenderMap(self.map,              renderer = ExportRenderer(dc, self.Map(), scale, offset,
306                                  (0,0,                                        region = (0, 0,
307                                      (width/self.scale)*scale,                                                  (width/self.scale)*scale,
308                                      (height/self.scale)*scale),                                                  (height/self.scale)*scale),
309                                  mapregion,                                        destination_region = mapregion)
310                                  selected_layer, selected_shapes)              renderer.RenderMap(selected_layer, selected_shapes)
311    
312              dc.EndDrawing()              dc.EndDrawing()
313              dc.Close()              dc.Close()
314          dlg.Destroy()          dlg.Destroy()
# Line 487  class MapCanvas(wxWindow, Publisher): Line 319  class MapCanvas(wxWindow, Publisher):
319          selected_layer = self.selection.SelectedLayer()          selected_layer = self.selection.SelectedLayer()
320          selected_shapes = self.selection.SelectedShapes()          selected_shapes = self.selection.SelectedShapes()
321                    
322          printout = MapPrintout(self, self.map, (0, 0, width, height),          printout = MapPrintout(self, self.Map(), (0, 0, width, height),
323                                 selected_layer, selected_shapes)                                 selected_layer, selected_shapes)
324          printer.Print(self, printout, wx.true)          printer.Print(self, printout, True)
325          printout.Destroy()          printout.Destroy()
326    
     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  
   
327      def redraw(self, *args):      def redraw(self, *args):
328          self.Refresh(0)          self.Refresh(False)
329    
330      def full_redraw(self, *args):      def full_redraw(self, *args):
331          self.bitmap = None          self.bitmap = None
332            self.selection_bitmap = None
333            self.render_iter = None
334          self.redraw()          self.redraw()
335    
336      def projection_changed(self, *args):      def redraw_selection(self, *args):
337          self.FitMapToWindow()          self.selection_bitmap = None
338          self.full_redraw()          self.render_iter = None
339            self.redraw()
340    
341      def set_view_transform(self, scale, offset):      def map_projection_changed(self, map, old_proj):
342          self.scale = scale          ViewPort.map_projection_changed(self, map, old_proj)
         self.offset = offset  
343          self.full_redraw()          self.full_redraw()
         self.issue(SCALE_CHANGED, scale)  
   
     def proj_to_win(self, x, y):  
         """\  
         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)  
   
     def win_to_proj(self, x, y):  
         """\  
         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)  
   
     def FitRectToWindow(self, rect):  
         """Fit the rectangular region given by rect into the window.  
   
         Set scale so that rect (in projected coordinates) just fits into  
         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))  
   
     def FitMapToWindow(self):  
         """Fit the map to the window  
   
         Set the scale so that the map fits exactly into the window and  
         center it in the window.  
         """  
         bbox = self.map.ProjectedBoundingBox()  
         if bbox is not None:  
             self.FitRectToWindow(bbox)  
   
     def FitLayerToWindow(self, layer):  
         """Fit the given layer to the window.  
   
         Set the scale so that the layer fits exactly into the window and  
         center it in the window.  
         """  
           
         bbox = layer.LatLongBoundingBox()  
         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 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  
         width, height = self.GetSizeTuple()  
         llx, lly = self.win_to_proj(0, height - 1)  
         urx, ury = self.win_to_proj(width - 1, 0)  
344    
345          sx, sy, ex, ey = rect      def layer_projection_changed(self, *args):
346          scalex = (ex - sx) / (urx - llx)          ViewPort.layer_projection_changed(self, args)
347          scaley = (ey - sy) / (ury - lly)          self.full_redraw()
         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  
   
     def ZoomInTool(self):  
         """Start the zoom in tool"""  
         self.SelectTool(ZoomInTool(self))  
   
     def ZoomOutTool(self):  
         """Start the zoom out tool"""  
         self.SelectTool(ZoomOutTool(self))  
   
     def PanTool(self):  
         """Start the pan tool"""  
         self.SelectTool(PanTool(self))  
         #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  
   
     def CurrentPosition(self):  
         """Return current position of the mouse in projected coordinates.  
348    
349          The result is a 2-tuple of floats with the coordinates. If the      def set_view_transform(self, scale, offset):
350          mouse is not in the window, the result is None.          ViewPort.set_view_transform(self, scale, offset)
351          """          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  
352    
353      def set_current_position(self, event):      def GetPortSizeTuple(self):
354          """Set the current position from event          return self.GetSizeTuple()
   
         Should be called by all events that contain mouse positions  
         especially EVT_MOTION. The event paramete may be None to  
         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)  
355    
356      def OnLeftDown(self, event):      def OnLeftDown(self, event):
357          self.set_current_position(event)          self.MouseLeftDown(event)
358          if self.tool is not None:          if self.tool is not None:
359              self.drag_dc = wxClientDC(self)              self.drag_dc = wxClientDC(self)
360              self.drag_dc.SetLogicalFunction(wxINVERT)              self.drag_dc.SetLogicalFunction(wxINVERT)
361              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
             self.CaptureMouse()  
             self.tool.MouseDown(event)  
362              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
363                self.CaptureMouse()
364              self.dragging = 1              self.dragging = 1
365    
366      def OnLeftUp(self, event):      def OnLeftUp(self, event):
367          self.set_current_position(event)          """Handle EVT_LEFT_UP
368    
369            Release the mouse if it was captured, if a tool is active call
370            its Hide method and call self.MouseLeftUp.
371            """
372            # It's important that ReleaseMouse is called before MouseLeftUp.
373            # MouseLeftUp may pop up modal dialogs which leads to an
374            # effectively frozen X session because the user can only
375            # interact with the dialog but the mouse is still grabbed by the
376            # canvas.
377          if self.dragging:          if self.dragging:
378              self.ReleaseMouse()              if self.HasCapture():
379                    self.ReleaseMouse()
380              try:              try:
381                  self.tool.Hide(self.drag_dc)                  self.tool.Hide(self.drag_dc)
                 self.tool.MouseUp(event)  
382              finally:              finally:
383                  self.drag_dc = None                  self.drag_dc = None
384                  self.dragging = 0                  self.dragging = 0
385            self.MouseLeftUp(event)
386    
387      def OnMotion(self, event):      def OnMotion(self, event):
         self.set_current_position(event)  
388          if self.dragging:          if self.dragging:
389              self.tool.Hide(self.drag_dc)              self.tool.Hide(self.drag_dc)
390              self.tool.MouseMove(event)  
391            self.MouseMove(event)
392    
393            if self.dragging:
394              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
395    
396      def OnLeaveWindow(self, event):      def OnLeaveWindow(self, event):
# Line 753  class MapCanvas(wxWindow, Publisher): Line 404  class MapCanvas(wxWindow, Publisher):
404          # Even when the window becomes larger some parts of the bitmap          # Even when the window becomes larger some parts of the bitmap
405          # could be reused.          # could be reused.
406          self.full_redraw()          self.full_redraw()
         pass  
407    
408      def shape_selected(self, layer, shape):      def shape_selected(self, layer, shape):
409          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
410          # The selection object takes care that it only issues          # The selection object takes care that it only issues
411          # SHAPES_SELECTED messages when the set of selected shapes has          # SHAPES_SELECTED messages when the set of selected shapes has
412          # actually changed, so we can do a full redraw unconditionally.          # actually changed, so we can do a full redraw of the
413          # FIXME: We should perhaps try to limit the redraw to the are          # selection_bitmap unconditionally.
414          # actually covered by the shapes before and after the selection          ViewPort.shape_selected(self, layer, shape)
415          # change.          self.redraw_selection()
416          self.full_redraw()  
417        def GetTextExtent(self, text):
418      def unprojected_rect_around_point(self, x, y, dist):          dc = wxClientDC(self)
419          """return a rect dist pixels around (x, y) in unprojected corrdinates          font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
420            dc.SetFont(font)
421          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  
   
         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.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)  
422    
423          If layer is given, only search in that layer. If no layer is      def LabelShapeAt(self, x, y, text=None):
         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):  
424          """Add or remove a label at window position x, y.          """Add or remove a label at window position x, y.
425    
426          If there's a label at the given position, remove it. Otherwise          If there's a label at the given position, remove it. Otherwise
427          determine the shape at the position, run the label dialog and          determine the shape at the position, run the label dialog and
428          unless the user cancels the dialog, add a laber.          unless the user cancels the dialog, add a label.
429          """          """
         ox = x; oy = y  
430          label_layer = self.map.LabelLayer()          label_layer = self.map.LabelLayer()
431          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
432          if layer is None and shape_index is not None:          if layer is None and shape_index is not None:
433              # a label was selected              ViewPort.LabelShapeAt(self, x, y)
             label_layer.RemoveLabel(shape_index)  
434          elif layer is not None:          elif layer is not None:
435              text = labeldialog.run_label_dialog(self, layer.table, shape_index)              text = labeldialog.run_label_dialog(self,
436              if text:                                                  layer.ShapeStore().Table(),
437                  proj = self.map.projection                                                  shape_index)
438                  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)  
   
 def OutputTransform(canvas_scale, canvas_offset, canvas_size, device_extend):  
     """Calculate dimensions to transform canvas content to output device."""  
     width, height = device_extend  
   
     # Only 80 % of the with are available for the map  
     width = width * 0.8  
   
     # Define the distance of the map from DC border  
     distance = 20  
   
     if height < width:  
         # landscape  
         map_height = height - 2*distance  
         map_width = map_height  
     else:  
         # portrait, recalibrate width (usually the legend width is too  
         # small  
         width = width * 0.9  
         map_height = width - 2*distance  
         map_width = map_height  
       
     mapregion = (distance, distance,  
                  distance+map_width, distance+map_height)  
   
     canvas_width, canvas_height = canvas_size  
       
     scalex = map_width / (canvas_width/canvas_scale)  
     scaley = map_height / (canvas_height/canvas_scale)  
     scale = min(scalex, scaley)  
     canvas_offx, canvas_offy = canvas_offset  
     offx = scale*canvas_offx/canvas_scale  
     offy = scale*canvas_offy/canvas_scale  
   
     return scale, (offx, offy), mapregion  

Legend:
Removed from v.926  
changed lines
  Added in v.2454

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26