/[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 1111 by bh, Fri May 30 09:54:48 2003 UTC revision 2709 by dpinte, Tue Oct 3 09:34:51 2006 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH  # opyright (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  import wx
   
 from wxPython.wx import wxWindow,\  
      wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\  
      EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \  
      wxBITMAP_TYPE_XPM, wxBeginBusyCursor, wxEndBusyCursor, wxCursor, \  
      wxImageFromBitmap, wxPlatform  
24    
25  # Export related stuff  # Export related stuff
26  if wxPlatform == '__WXMSW__':  if wx.Platform == '__WXMSW__':
27      from wxPython.wx import wxMetaFileDC      from wx import MetaFileDC
28  from wxPython.wx import wxFileDialog, wxSAVE, wxOVERWRITE_PROMPT, wxID_OK  
29    from Thuban import _
 from wxPython import wx  
   
 from wxproj import point_in_polygon_shape, shape_centroid  
   
 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  
30    
31  import resource  from Thuban.Model.messages import MAP_LAYERS_CHANGED, LAYER_CHANGED, \
32         LAYER_VISIBILITY_CHANGED
33    
 from selection import Selection  
34  from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer  from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
35    
36  import labeldialog  import labeldialog
37    
38  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  
   
     def draw(self, dc):  
         pass  
   
     def MouseDown(self, event):  
         self.drag_start(event.m_x, event.m_y)  
   
     def MouseMove(self, event):  
         if self.dragging:  
             self.drag_move(event.m_x, event.m_y)  
   
     def MouseUp(self, event):  
         if self.dragging:  
             self.drag_move(event.m_x, event.m_y)  
   
     def Cancel(self):  
         self.dragging = 0  
   
39    
40  class RectTool(Tool):  class CanvasPanTool(PanTool):
41    
42      """Base class for tools that draw rectangles while dragging"""      """The Canvas Pan Tool"""
   
     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"  
43    
44      def MouseMove(self, event):      def MouseMove(self, event):
45          if self.dragging:          if self.dragging:
46              Tool.MouseMove(self, event)              PanTool.MouseMove(self, event)
47              sx, sy = self.start              sx, sy = self.start
48              x, y = self.current              x, y = self.current
49              width, height = self.view.GetSizeTuple()              width, height = self.view.GetSizeTuple()
50    
51              bitmapdc = wx.wxMemoryDC()              bitmapdc = wx.MemoryDC()
52              bitmapdc.SelectObject(self.view.bitmap)              bitmapdc.SelectObject(self.view.PreviewBitmap())
53    
54              dc = self.view.drag_dc              dc = self.view.drag_dc
55              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
56    
57      def MouseUp(self, event):  class MapPrintout(wx.Printout):
         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)  
   
   
 class MapPrintout(wx.wxPrintout):  
58    
59      """      """
60      wxPrintout class for printing Thuban maps      wxPrintout class for printing Thuban maps
61      """      """
62    
63      def __init__(self, canvas, map, region, selected_layer, selected_shapes):      def __init__(self, canvas, map, region, selected_layer, selected_shapes):
64          wx.wxPrintout.__init__(self)          wx.Printout.__init__(self)
65          self.canvas = canvas          self.canvas = canvas
66          self.map = map          self.map = map
67          self.region = region          self.region = region
# Line 257  class MapPrintout(wx.wxPrintout): Line 80  class MapPrintout(wx.wxPrintout):
80    
81      def draw_on_dc(self, dc):      def draw_on_dc(self, dc):
82          width, height = self.GetPageSizePixels()          width, height = self.GetPageSizePixels()
83          scale, offset, mapregion = OutputTransform(self.canvas.scale,          scale, offset, mapregion = output_transform(self.canvas.scale,
84                                                     self.canvas.offset,                                                      self.canvas.offset,
85                                                     self.canvas.GetSizeTuple(),                                                      self.canvas.GetSizeTuple(),
86                                                     self.GetPageSizePixels())                                                      self.GetPageSizePixels())
87          resx, resy = self.GetPPIPrinter()          resx, resy = self.GetPPIPrinter()
         renderer = PrinterRenderer(dc, scale, offset, resolution = resy)  
         x, y, width, height = self.region  
88          canvas_scale = self.canvas.scale          canvas_scale = self.canvas.scale
89          renderer.RenderMap(self.map,          x, y, width, height = self.region
90                             (0,0,          renderer = PrinterRenderer(dc, self.map, scale, offset,
91                                  (width/canvas_scale)*scale,                                     region = (mapregion[0], mapregion[1],
92                                  (height/canvas_scale)*scale),                                               (width/canvas_scale)*scale,
93                                  mapregion,                                               (height/canvas_scale)*scale),
94                             self.selected_layer, self.selected_shapes)                                     resolution = resy,
95                                       destination_region = mapregion)
96            renderer.RenderMap(self.selected_layer, self.selected_shapes)
97          return True          return True
98    
99    
100  class MapCanvas(wxWindow, Publisher):  class MapCanvas(wx.Window, ViewPort):
101    
102      """A widget that displays a map and offers some interaction"""      """A widget that displays a map and offers some interaction"""
103    
     # 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"}  
   
104      def __init__(self, parent, winid):      def __init__(self, parent, winid):
105          wxWindow.__init__(self, parent, winid)          wx.Window.__init__(self, parent, winid)
106          self.SetBackgroundColour(wxColour(255, 255, 255))          ViewPort.__init__(self)
   
         # the map displayed in this canvas. Set with SetMap()  
         self.map = None  
107    
108          # scale and offset describe the transformation from projected          self.SetBackgroundColour(wx.Colour(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  
109    
110          # the bitmap serving as backing store          # the bitmap serving as backing store
111          self.bitmap = None          self.bitmap = None
112            # the monochrome bitmap with the selection if any
113            self.selection_bitmap = None
114    
115          # the selection          self.backgroundColor = wx.WHITE_BRUSH
116          self.selection = Selection()  
117          self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)          # The rendering iterator object. Used when rendering
118            # incrementally
119          # keep track of which layers/shapes are selected to make sure we          self.render_iter = None
         # only redraw when necessary  
         self.last_selected_layer = None  
         self.last_selected_shape = None  
120    
121          # subscribe the WX events we're interested in          # subscribe the WX events we're interested in
122          EVT_PAINT(self, self.OnPaint)          self.Bind(wx.EVT_PAINT, self.OnPaint)
123          EVT_LEFT_DOWN(self, self.OnLeftDown)          self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
124          EVT_LEFT_UP(self, self.OnLeftUp)          self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
125          EVT_MOTION(self, self.OnMotion)          self.Bind(wx.EVT_MIDDLE_DOWN, self.OnMiddleDown)
126          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)          self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp)
127          wx.EVT_SIZE(self, self.OnSize)          self.Bind(wx.EVT_MOTION, self.OnMotion)
128            self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
129            self.Bind(wx.EVT_SIZE, self.OnSize)
130            self.Bind(wx.EVT_IDLE, self.OnIdle)
131    
132      def __del__(self):      def __del__(self):
133          wxWindow.__del__(self)          wx.Window.__del__(self)
134          Publisher.__del__(self)          ViewPort.__del__(self)
135    
136      def Subscribe(self, channel, *args):      def PreviewBitmap(self):
137          """Extend the inherited method to handle delegated messages.          return self.bitmap
138    
139          If channel is one of the delegated messages call the appropriate      def PanTool(self):
140          object's Subscribe method. Otherwise just call the inherited          """Start the canvas pan tool"""
141          method.          self.SelectTool(CanvasPanTool(self))
         """  
         if channel in self.delegated_messages:  
             object = getattr(self, self.delegated_messages[channel])  
             object.Subscribe(channel, *args)  
         else:  
             Publisher.Subscribe(self, channel, *args)  
142    
143      def Unsubscribe(self, channel, *args):      def SetMap(self, map):
144          """Extend the inherited method to handle delegated messages.          redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
145                               LAYER_VISIBILITY_CHANGED)
146            if self.Map() is not None:
147                for channel in redraw_channels:
148                    self.Map().Unsubscribe(channel, self.full_redraw)
149    
150          If channel is one of the delegated messages call the appropriate          ViewPort.SetMap(self, map)
         object's Unsubscribe method. Otherwise just call the inherited  
         method.  
         """  
         if channel in self.delegated_messages:  
             object = getattr(self, self.delegated_messages[channel])  
             object.Unsubscribe(channel, *args)  
         else:  
             Publisher.Unsubscribe(self, channel, *args)  
151    
152      def __getattr__(self, attr):          if self.Map() is not None:
153          if attr in self.delegated_methods:              for channel in redraw_channels:
154              return getattr(getattr(self, self.delegated_methods[attr]), attr)                  self.Map().Subscribe(channel, self.full_redraw)
         raise AttributeError(attr)  
155    
156      def OnPaint(self, event):          # force a redraw. If map is not empty, it's already been called
157          dc = wxPaintDC(self)          # by FitMapToWindow but if map is empty it hasn't been called
158          clear = self.map is None or not self.map.HasLayers()          # yet so we have to explicitly call it.
159            self.full_redraw()
160    
161          wxBeginBusyCursor()      def OnPaint(self, event):
162          try:          dc = wx.PaintDC(self)
163              if not clear:          if self.Map() is not None and self.Map().HasLayers():
164                  self.do_redraw()              if self.bitmap is not None:
                 try:  
                     pass  
                 except:  
                     print "Error during drawing:", sys.exc_info()[0]  
                     clear = True  
   
             if clear:  
                 # If we've got no map or if the map is empty, simply clear  
                 # the screen.  
   
                 # XXX it's probably possible to get rid of this. The  
                 # background color of the window is already white and the  
                 # only thing we may have to do is to call self.Refresh()  
                 # with a true argument in the right places.  
165                  dc.BeginDrawing()                  dc.BeginDrawing()
166                  dc.Clear()                  dc.DrawBitmap(self.bitmap, 0, 0)
167                    if self.selection_bitmap is not None:
168                        dc.DrawBitmap(self.selection_bitmap, 0, 0, True)
169                  dc.EndDrawing()                  dc.EndDrawing()
170          finally:          else:
171              wxEndBusyCursor()              # If we've got no map or if the map is empty, simply clear
172                # the screen.
173    
174      def do_redraw(self):              # XXX it's probably possible to get rid of this. The
175          # This should only be called if we have a non-empty map.              # background color of the window is already white and the
176                # only thing we may have to do is to call self.Refresh()
177                # with a true argument in the right places.
178                dc.BeginDrawing()
179                dc.SetBackground(self.backgroundColor)
180                dc.Clear()
181                dc.EndDrawing()
182    
183        def OnIdle(self, event):
184            """Idle handler. Redraw the bitmap if necessary"""
185            if (self.Map() is not None
186                and (self.bitmap is None
187                     or self.render_iter is not None
188                     or (self.HasSelectedShapes()
189                         and self.selection_bitmap is None))):
190                event.RequestMore(self._do_redraw())
191    
192        def _do_redraw(self):
193            """Redraw a bit and return whether this method has to be called again.
194    
195            Called by OnIdle to handle the actual redraw. Redraw is
196            incremental for both the bitmap with the normal layers and the
197            bitmap with the selection.
198            """
199            finished = False
200            if self.render_iter is not None:
201                try:
202                    if self.render_iter.next():
203                        # Redraw if the last preview redraw was some time
204                        # ago and the user is not currently dragging the
205                        # mouse because redrawing would interfere with what
206                        # the current tool is drawing on the window.
207                        if not self.dragging \
208                               and time.time() - self.render_last_preview > 0.5:
209                            client_dc = wx.ClientDC(self)
210                            client_dc.BeginDrawing()
211                            client_dc.DrawBitmap(self.bitmap, 0, 0)
212                            client_dc.EndDrawing()
213                            self.render_last_preview = time.time()
214                    else:
215                        self.render_iter = None
216                        # Redraw if not dragging because redrawing would
217                        # interfere with what the current tool is drawing on
218                        # the window.
219                        if not self.dragging:
220                            self.redraw()
221                        finished = True
222                except StopIteration:
223                    finished = True
224                    self.render_iter = None
225                except:
226                    finished = True
227                    self.render_iter = None
228                    traceback.print_exc()
229            else:
230                self.render_iter = self._render_iterator()
231                self.render_last_preview = time.time()
232            return not finished
233    
234          # Get the window size.      def _render_iterator(self):
235          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
236            dc = wx.MemoryDC()
237    
238          # If self.bitmap's still there, reuse it. Otherwise redraw it          render_start = time.time()
239          if self.bitmap is not None:  
240              bitmap = self.bitmap          if self.bitmap is None:
241          else:              self.bitmap = wx.EmptyBitmap(width, height)
242              bitmap = wx.wxEmptyBitmap(width, height)              dc.SelectObject(self.bitmap)
             dc = wx.wxMemoryDC()  
             dc.SelectObject(bitmap)  
243              dc.BeginDrawing()              dc.BeginDrawing()
244    
245              # 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)  
246              dc.Clear()              dc.Clear()
247    
             selected_layer = self.selection.SelectedLayer()  
             selected_shapes = self.selection.SelectedShapes()  
   
248              # draw the map into the bitmap              # draw the map into the bitmap
249              renderer = ScreenRenderer(dc, self.scale, self.offset)              renderer = ScreenRenderer(dc, self.Map(), self.scale, self.offset,
250                                          (0, 0, width, height))
251                for cont in renderer.RenderMapIncrementally():
252                    yield True
253    
254              # Pass the entire bitmap as update region to the renderer.              dc.EndDrawing()
255              # We're redrawing the whole bitmap, after all.              dc.SelectObject(wx.NullBitmap)
256              renderer.RenderMap(self.map, (0, 0, width, height),  
257                                 selected_layer, selected_shapes)          if self.HasSelectedShapes() and self.selection_bitmap is None:
258                bitmap = wx.EmptyBitmap(width, height)
259                dc.SelectObject(bitmap)
260                dc.BeginDrawing()
261                dc.SetBackground(wx.WHITE_BRUSH)
262                dc.Clear()
263    
264                renderer = ScreenRenderer(dc, self.Map(), self.scale, self.offset,
265                                          (0, 0, width, height))
266                layer = self.SelectedLayer()
267                shapes = self.selection.SelectedShapes()
268                for cont in renderer.draw_selection_incrementally(layer, shapes):
269                    yield True
270    
271              dc.EndDrawing()              dc.EndDrawing()
272              dc.SelectObject(wx.wxNullBitmap)              dc.SelectObject(wx.NullBitmap)
273              self.bitmap = bitmap  
274                bitmap.SetMask(wx.Mask(bitmap, wx.WHITE))
275                self.selection_bitmap = bitmap
276    
277          # blit the bitmap to the screen          yield False
         dc = wx.wxMemoryDC()  
         dc.SelectObject(bitmap)  
         clientdc = wxClientDC(self)  
         clientdc.BeginDrawing()  
         clientdc.Blit(0, 0, width, height, dc, 0, 0)  
         clientdc.EndDrawing()  
278    
279      def Export(self):      def Export(self):
         if self.scale == 0:  
             return  
280    
281          if hasattr(self, "export_path"):          if hasattr(self, "export_path"):
282              export_path = self.export_path              export_path = self.export_path
283          else:          else:
284              export_path="."              export_path="."
285          dlg = wxFileDialog(self, _("Export Map"), export_path, "",          dlg = wx.FileDialog(self, _("Export Map"), export_path, "",
286                             "Enhanced Metafile (*.wmf)|*.wmf",                             "Enhanced Metafile (*.wmf)|*.wmf",
287                             wxSAVE|wxOVERWRITE_PROMPT)                             wx.SAVE|wx.OVERWRITE_PROMPT)
288          if dlg.ShowModal() == wxID_OK:          if dlg.ShowModal() == wx.ID_OK:
289              self.export_path = os.path.dirname(dlg.GetPath())              self.export_path = os.path.dirname(dlg.GetPath())
290              dc = wxMetaFileDC(dlg.GetPath())              dc = wx.MetaFileDC(dlg.GetPath())
291        
292              scale, offset, mapregion = OutputTransform(self.scale,              scale, offset, mapregion = output_transform(self.scale,
293                                                         self.offset,                                                          self.offset,
294                                                         self.GetSizeTuple(),                                                          self.GetSizeTuple(),
295                                                         dc.GetSizeTuple())                                                          dc.GetSizeTuple())
296    
297              selected_layer = self.selection.SelectedLayer()              selected_layer = self.selection.SelectedLayer()
298              selected_shapes = self.selection.SelectedShapes()              selected_shapes = self.selection.SelectedShapes()
299    
             renderer = ExportRenderer(dc, scale, offset)  
   
             # Pass the entire bitmap as update region to the renderer.  
             # We're redrawing the whole bitmap, after all.  
300              width, height = self.GetSizeTuple()              width, height = self.GetSizeTuple()
301              renderer.RenderMap(self.map,              renderer = ExportRenderer(dc, self.Map(), scale, offset,
302                                  (0,0,                                        region = (0, 0,
303                                      (width/self.scale)*scale,                                                  (width/self.scale)*scale,
304                                      (height/self.scale)*scale),                                                  (height/self.scale)*scale),
305                                  mapregion,                                        destination_region = mapregion)
306                                  selected_layer, selected_shapes)              renderer.RenderMap(selected_layer, selected_shapes)
307    
308              dc.EndDrawing()              dc.EndDrawing()
309              dc.Close()              dc.Close()
310          dlg.Destroy()          dlg.Destroy()
311            
312      def Print(self):      def Print(self):
313          printer = wx.wxPrinter()          printer = wx.Printer()
314          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
315          selected_layer = self.selection.SelectedLayer()          selected_layer = self.selection.SelectedLayer()
316          selected_shapes = self.selection.SelectedShapes()          selected_shapes = self.selection.SelectedShapes()
317            
318          printout = MapPrintout(self, self.map, (0, 0, width, height),          printout = MapPrintout(self, self.Map(), (0, 0, width, height),
319                                 selected_layer, selected_shapes)                                 selected_layer, selected_shapes)
320          printer.Print(self, printout, True)          printer.Print(self, printout, True)
321          printout.Destroy()          printout.Destroy()
322    
     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  
   
323      def redraw(self, *args):      def redraw(self, *args):
324          self.Refresh(0)          self.Refresh(False)
325    
326      def full_redraw(self, *args):      def full_redraw(self, *args):
327          self.bitmap = None          self.bitmap = None
328            self.selection_bitmap = None
329            self.render_iter = None
330          self.redraw()          self.redraw()
331    
332      def projection_changed(self, *args):      def redraw_selection(self, *args):
333          self.FitMapToWindow()          self.selection_bitmap = None
334          self.full_redraw()          self.render_iter = None
335            self.redraw()
     def set_view_transform(self, scale, offset):  
         # width/height of the projected bbox  
         llx, lly, urx, ury = bbox = self.map.ProjectedBoundingBox()  
         pwidth = float(urx - llx)  
         pheight = float(ury - lly)  
   
         # width/height of the window  
         wwidth, wheight = self.GetSizeTuple()  
   
         # The window's center in projected coordinates assuming the new  
         # scale/offset  
         pcenterx = (wwidth/2 - offset[0]) / scale  
         pcentery = (offset[1] - wheight/2) / scale  
   
         # The window coordinates used when drawing the shapes must fit  
         # into 16bit signed integers.  
         max_len = max(pwidth, pheight)  
         if max_len:  
             max_scale = 32000.0 / max_len  
         else:  
             # FIXME: What to do in this case? The bbox is effectively  
             # empty so any scale should work.  
             max_scale = scale  
   
         # The minimal scale is somewhat arbitrarily set to half that of  
         # the bbox fit into the window  
         scales = []  
         if pwidth:  
             scales.append(wwidth / pwidth)  
         if pheight:  
             scales.append(wheight / pheight)  
         if scales:  
             min_scale = 0.5 * min(scales)  
         else:  
             min_scale = scale  
336    
337          if scale > max_scale:      def map_projection_changed(self, map, old_proj):
338              scale = max_scale          ViewPort.map_projection_changed(self, map, old_proj)
         elif scale < min_scale:  
             scale = min_scale  
   
         self.scale = scale  
   
         # determine new offset to preserve the center  
         self.offset = (wwidth/2 - scale * pcenterx,  
                        wheight/2 + scale * pcentery)  
339          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  
         """  
         if self.scale == 0:  
             return (0, 0)  
   
         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  
         """  
         if self.scale == 0:  
             return (0, 0)  
   
         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:  
                 if len(shapes) == 1 and layer.ShapeType() == SHAPETYPE_POINT:  
                     self.ZoomFactor(1, self.proj_to_win(bbox[0], bbox[1]))  
                 else:  
                     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  
         """  
         if self.scale > 0:  
             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.  
340    
341          The rect parameter is given in window coordinates      def layer_projection_changed(self, *args):
342          """          ViewPort.layer_projection_changed(self, args)
343          # determine the bbox of the displayed region in projected          self.full_redraw()
         # coordinates  
         width, height = self.GetSizeTuple()  
         llx, lly = self.win_to_proj(0, height - 1)  
         urx, ury = self.win_to_proj(width - 1, 0)  
   
         sx, sy, ex, ey = rect  
         scalex = (ex - sx) / (urx - llx)  
         scaley = (ey - sy) / (ury - lly)  
         scale = min(scalex, scaley)  
   
         offx = 0.5 * ((ex + sx) - (urx + llx) * scale)  
         offy = 0.5 * ((ey + sy) + (ury + lly) * scale)  
         self.set_view_transform(scale, (offx, offy))  
   
     def Translate(self, dx, dy):  
         """Move the map by dx, dy pixels"""  
         offx, offy = self.offset  
         self.set_view_transform(self.scale, (offx + dx, offy + dy))  
   
     def SelectTool(self, tool):  
         """Make tool the active tool.  
   
         The parameter should be an instance of Tool or None to indicate  
         that no tool is active.  
         """  
         self.tool = tool  
   
     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.  
344    
345          The result is a 2-tuple of floats with the coordinates. If the      def set_view_transform(self, scale, offset):
346          mouse is not in the window, the result is None.          ViewPort.set_view_transform(self, scale, offset)
347          """          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  
348    
349      def set_current_position(self, event):      def GetPortSizeTuple(self):
350          """Set the current position from event          return self.GetSizeTuple()
351    
352          Should be called by all events that contain mouse positions      def OnMiddleDown(self, event):
353          especially EVT_MOTION. The event paramete may be None to          self.remembertool = self.tool
354          indicate the the pointer left the window.          if self.Map() is not None and self.Map().HasLayers():
355          """              self.PanTool()
356          if event is not None:              self.OnLeftDown(event)
357              self.current_position = (event.m_x, event.m_y)  
358          else:      def OnMiddleUp(self, event):
359              self.current_position = None          self.OnLeftUp(event)
360          self.issue(VIEW_POSITION)          if self.remembertool:
361                self.SelectTool(self.remembertool)
362    
363      def OnLeftDown(self, event):      def OnLeftDown(self, event):
364          self.set_current_position(event)          self.MouseLeftDown(event)
365          if self.tool is not None:          if self.tool is not None:
366              self.drag_dc = wxClientDC(self)              self.drag_dc = wx.ClientDC(self)
367              self.drag_dc.SetLogicalFunction(wxINVERT)              self.drag_dc.SetLogicalFunction(wx.INVERT)
368              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)              self.drag_dc.SetBrush(wx.TRANSPARENT_BRUSH)
             self.CaptureMouse()  
             self.tool.MouseDown(event)  
369              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
370                self.CaptureMouse()
371              self.dragging = 1              self.dragging = 1
372    
373      def OnLeftUp(self, event):      def OnLeftUp(self, event):
374          self.set_current_position(event)          """Handle EVT_LEFT_UP
375    
376            Release the mouse if it was captured, if a tool is active call
377            its Hide method and call self.MouseLeftUp.
378            """
379            # It's important that ReleaseMouse is called before MouseLeftUp.
380            # MouseLeftUp may pop up modal dialogs which leads to an
381            # effectively frozen X session because the user can only
382            # interact with the dialog but the mouse is still grabbed by the
383            # canvas.
384          if self.dragging:          if self.dragging:
385              self.ReleaseMouse()              if self.HasCapture():
386                    self.ReleaseMouse()
387              try:              try:
388                  self.tool.Hide(self.drag_dc)                  self.tool.Hide(self.drag_dc)
                 self.tool.MouseUp(event)  
389              finally:              finally:
390                  self.drag_dc = None                  self.drag_dc = None
391                  self.dragging = 0                  self.dragging = 0
392            self.MouseLeftUp(event)
393    
394      def OnMotion(self, event):      def OnMotion(self, event):
         self.set_current_position(event)  
395          if self.dragging:          if self.dragging:
396              self.tool.Hide(self.drag_dc)              self.tool.Hide(self.drag_dc)
397              self.tool.MouseMove(event)  
398            self.MouseMove(event)
399    
400            if self.dragging:
401              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
402    
403      def OnLeaveWindow(self, event):      def OnLeaveWindow(self, event):
# Line 809  class MapCanvas(wxWindow, Publisher): Line 411  class MapCanvas(wxWindow, Publisher):
411          # Even when the window becomes larger some parts of the bitmap          # Even when the window becomes larger some parts of the bitmap
412          # could be reused.          # could be reused.
413          self.full_redraw()          self.full_redraw()
         pass  
414    
415      def shape_selected(self, layer, shape):      def shape_selected(self, layer, shape):
416          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
417          # The selection object takes care that it only issues          # The selection object takes care that it only issues
418          # SHAPES_SELECTED messages when the set of selected shapes has          # SHAPES_SELECTED messages when the set of selected shapes has
419          # actually changed, so we can do a full redraw unconditionally.          # actually changed, so we can do a full redraw of the
420          # FIXME: We should perhaps try to limit the redraw to the are          # selection_bitmap unconditionally.
421          # actually covered by the shapes before and after the selection          ViewPort.shape_selected(self, layer, shape)
422          # change.          self.redraw_selection()
423          self.full_redraw()  
424        def GetTextExtent(self, text):
425      def unprojected_rect_around_point(self, x, y, dist):          dc = wx.ClientDC(self)
426          """return a rect dist pixels around (x, y) in unprojected corrdinates          font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL)
427            dc.SetFont(font)
428          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  
   
         if scale == 0:  
             return None, None  
   
         offx, offy = self.offset  
429    
430          if select_labels:      def LabelShapeAt(self, x, y, text=None):
             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)  
   
         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):  
431          """Add or remove a label at window position x, y.          """Add or remove a label at window position x, y.
432    
433          If there's a label at the given position, remove it. Otherwise          If there's a label at the given position, remove it. Otherwise
434          determine the shape at the position, run the label dialog and          determine the shape at the position, run the label dialog and
435          unless the user cancels the dialog, add a laber.          unless the user cancels the dialog, add a label.
436          """          """
         ox = x; oy = y  
         label_layer = self.map.LabelLayer()  
437          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
438          if layer is None and shape_index is not None:          if layer is None and shape_index is not None:
439              # a label was selected              ViewPort.LabelShapeAt(self, x, y)
             label_layer.RemoveLabel(shape_index)  
440          elif layer is not None:          elif layer is not None:
441              text = labeldialog.run_label_dialog(self, layer.table, shape_index)              text = labeldialog.run_label_dialog(self,
442              if text:                                                  layer.ShapeStore().Table(),
443                  proj = self.map.projection                                                  shape_index)
444                  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  
445    
                 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  
446    
     return scale, (offx, offy), mapregion  

Legend:
Removed from v.1111  
changed lines
  Added in v.2709

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26