/[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 610 by jonathan, Fri Apr 4 13:56:59 2003 UTC revision 1460 by bh, Fri Jul 18 15:23:48 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2  # Authors:  # Authors:
3  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
4    # Frank Koormann <[email protected]>
5  #  #
6  # This program is free software under the GPL (>=v2)  # This program is free software under the GPL (>=v2)
7  # Read the file COPYING coming with Thuban for details.  # Read the file COPYING coming with Thuban for details.
# Line 11  Classes for display of a map and interac Line 12  Classes for display of a map and interac
12    
13  __version__ = "$Revision$"  __version__ = "$Revision$"
14    
15  from math import hypot  from Thuban import _
16    
17  from wxPython.wx import wxWindow,\  import os.path
      wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\  
      EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW  
18    
19    from wxPython.wx import wxWindow, \
20         wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
21         EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
22         wxPlatform, wxBeginBusyCursor, wxEndBusyCursor, wxFileDialog, wxSAVE, \
23         wxOVERWRITE_PROMPT, wxID_OK
24    
25    # Export related stuff
26    if wxPlatform == '__WXMSW__':
27        from wxPython.wx import wxMetaFileDC
28    
29  from wxPython import wx  from wxPython import wx
30    
31  from wxproj import point_in_polygon_shape, shape_centroid  from Thuban.Model.messages import MAP_LAYERS_CHANGED, LAYER_CHANGED, \
32         LAYER_VISIBILITY_CHANGED
33    
34  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \  from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
      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  
   
 from selection import Selection  
 from renderer import ScreenRenderer, PrinterRender  
35    
36  import labeldialog  import labeldialog
37    
38  from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION  from viewport import ViewPort, PanTool, output_transform
   
   
 #  
 #   The tools  
 #  
   
 class Tool:  
   
     """  
     Base class for the interactive tools  
     """  
   
     def __init__(self, view):  
         """Intitialize the tool. The view is the canvas displaying the map"""  
         self.view = view  
         self.start = self.current = None  
         self.dragging = 0  
         self.drawn = 0  
   
     def Name(self):  
         """Return the tool's name"""  
         return ''  
   
     def drag_start(self, x, y):  
         self.start = self.current = x, y  
         self.dragging = 1  
   
     def drag_move(self, x, y):  
         self.current = x, y  
   
     def drag_stop(self, x, y):  
         self.current = x, y  
         self.dragging = 0  
   
     def Show(self, dc):  
         if not self.drawn:  
             self.draw(dc)  
         self.drawn = 1  
   
     def Hide(self, dc):  
         if self.drawn:  
             self.draw(dc)  
         self.drawn = 0  
   
     def draw(self, dc):  
         pass  
   
     def MouseDown(self, event):  
         self.drag_start(event.m_x, event.m_y)  
   
     def MouseMove(self, event):  
         if self.dragging:  
             self.drag_move(event.m_x, event.m_y)  
   
     def MouseUp(self, event):  
         if self.dragging:  
             self.drag_move(event.m_x, event.m_y)  
   
     def Cancel(self):  
         self.dragging = 0  
   
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()
# Line 188  class PanTool(Tool): Line 54  class PanTool(Tool):
54              dc = self.view.drag_dc              dc = self.view.drag_dc
55              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
56    
     def MouseUp(self, event):  
         if self.dragging:  
             Tool.MouseUp(self, event)  
             sx, sy = self.start  
             cx, cy = self.current  
             self.view.Translate(cx - sx, cy - sy)  
   
 class IdentifyTool(Tool):  
   
     """The "Identify" Tool"""  
   
     def Name(self):  
         return "IdentifyTool"  
   
     def MouseUp(self, event):  
         self.view.SelectShapeAt(event.m_x, event.m_y)  
   
   
 class LabelTool(Tool):  
   
     """The "Label" Tool"""  
   
     def Name(self):  
         return "LabelTool"  
   
     def MouseUp(self, event):  
         self.view.LabelShapeAt(event.m_x, event.m_y)  
   
   
   
   
57  class MapPrintout(wx.wxPrintout):  class MapPrintout(wx.wxPrintout):
58    
59      """      """
60      wxPrintout class for printing Thuban maps      wxPrintout class for printing Thuban maps
61      """      """
62    
63      def __init__(self, map):      def __init__(self, canvas, map, region, selected_layer, selected_shapes):
64          wx.wxPrintout.__init__(self)          wx.wxPrintout.__init__(self)
65            self.canvas = canvas
66          self.map = map          self.map = map
67            self.region = region
68            self.selected_layer = selected_layer
69            self.selected_shapes = selected_shapes
70    
71      def GetPageInfo(self):      def GetPageInfo(self):
72          return (1, 1, 1, 1)          return (1, 1, 1, 1)
# Line 241  class MapPrintout(wx.wxPrintout): Line 80  class MapPrintout(wx.wxPrintout):
80    
81      def draw_on_dc(self, dc):      def draw_on_dc(self, dc):
82          width, height = self.GetPageSizePixels()          width, height = self.GetPageSizePixels()
83          llx, lly, urx, ury = self.map.ProjectedBoundingBox()          scale, offset, mapregion = output_transform(self.canvas.scale,
84          scalex = width / (urx - llx)                                                      self.canvas.offset,
85          scaley = height / (ury - lly)                                                      self.canvas.GetSizeTuple(),
86          scale = min(scalex, scaley)                                                      self.GetPageSizePixels())
         offx = 0.5 * (width - (urx + llx) * scale)  
         offy = 0.5 * (height + (ury + lly) * scale)  
   
87          resx, resy = self.GetPPIPrinter()          resx, resy = self.GetPPIPrinter()
88          renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)          renderer = PrinterRenderer(dc, scale, offset, resolution = resy)
89          renderer.RenderMap(self.map)          x, y, width, height = self.region
90          return wx.true          canvas_scale = self.canvas.scale
91            renderer.RenderMap(self.map,
92                               (0,0,
93                                    (width/canvas_scale)*scale,
94                                    (height/canvas_scale)*scale),
95                                    mapregion,
96                               self.selected_layer, self.selected_shapes)
97            return True
98    
99  class MapCanvas(wxWindow, Publisher):  class MapCanvas(wxWindow, ViewPort):
100    
101      """A widget that displays a map and offers some interaction"""      """A widget that displays a map and offers some interaction"""
102    
     # Some messages that can be subscribed/unsubscribed directly through  
     # the MapCanvas come in fact from other objects. This is a map to  
     # map those messages to the names of the instance variables they  
     # actually come from. This delegation is implemented in the  
     # Subscribe and unsubscribed 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"}  
   
103      def __init__(self, parent, winid):      def __init__(self, parent, winid):
104          wxWindow.__init__(self, parent, winid)          wxWindow.__init__(self, parent, winid)
105          self.SetBackgroundColour(wxColour(255, 255, 255))          ViewPort.__init__(self)
   
         # the map displayed in this canvas. Set with SetMap()  
         self.map = None  
106    
107          # scale and offset describe the transformation from projected          self.SetBackgroundColour(wxColour(255, 255, 255))
         # coordinates to window coordinates.  
         self.scale = 1.0  
         self.offset = (0, 0)  
   
         # whether the user is currently dragging the mouse, i.e. moving  
         # the mouse while pressing a mouse button  
         self.dragging = 0  
   
         # the currently active tool  
         self.tool = None  
   
         # The current mouse position of the last OnMotion event or None  
         # if the mouse is outside the window.  
         self.current_position = None  
108    
109          # the bitmap serving as backing store          # the bitmap serving as backing store
110          self.bitmap = None          self.bitmap = None
111    
112          # the selection          self.backgroundColor = wx.wxWHITE_BRUSH
         self.selection = Selection()  
         self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)  
   
         # 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  
113    
114          # subscribe the WX events we're interested in          # subscribe the WX events we're interested in
115          EVT_PAINT(self, self.OnPaint)          EVT_PAINT(self, self.OnPaint)
# Line 315  class MapCanvas(wxWindow, Publisher): Line 118  class MapCanvas(wxWindow, Publisher):
118          EVT_MOTION(self, self.OnMotion)          EVT_MOTION(self, self.OnMotion)
119          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
120          wx.EVT_SIZE(self, self.OnSize)          wx.EVT_SIZE(self, self.OnSize)
121            wx.EVT_IDLE(self, self.OnIdle)
122    
123      def __del__(self):      def __del__(self):
124          wxWindow.__del__(self)          wxWindow.__del__(self)
125          Publisher.__del__(self)          ViewPort.__del__(self)
126    
127      def Subscribe(self, channel, *args):      def PanTool(self):
128          """Extend the inherited method to handle delegated messages.          """Start the canvas pan tool"""
129            self.SelectTool(CanvasPanTool(self))
130          If channel is one of the delegated messages call the appropriate          
131          object's Subscribe method. Otherwise just call the inherited      def SetMap(self, map):
132          method.          redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
133          """                             LAYER_VISIBILITY_CHANGED)
134          if channel in self.delegated_messages:          if self.Map() is not None:
135              object = getattr(self, self.delegated_messages[channel])              for channel in redraw_channels:
136              object.Subscribe(channel, *args)                  self.Map().Unsubscribe(channel, self.full_redraw)
         else:  
             Publisher.Subscribe(self, channel, *args)  
137    
138      def Unsubscribe(self, channel, *args):          ViewPort.SetMap(self, map)
         """Extend the inherited method to handle delegated messages.  
139    
140          If channel is one of the delegated messages call the appropriate          if self.Map() is not None:
141          object's Unsubscribe method. Otherwise just call the inherited              for channel in redraw_channels:
142          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)  
143    
144      def __getattr__(self, attr):          # force a redraw. If map is not empty, it's already been called
145          if attr in self.delegated_methods:          # by FitMapToWindow but if map is empty it hasn't been called
146              return getattr(getattr(self, self.delegated_methods[attr]), attr)          # yet so we have to explicitly call it.
147          raise AttributeError(attr)          self.full_redraw()
148    
149      def OnPaint(self, event):      def OnPaint(self, event):
150          dc = wxPaintDC(self)          dc = wxPaintDC(self)
151          if self.map is not None and self.map.HasLayers():  
152              self.do_redraw()          if self.Map() is not None and self.Map().HasLayers():
153                if self.bitmap in (None, -1):
154                    # set the flag that we should redraw the
155                    # bitmap in idle time
156                    self.bitmap = -1
157                else:
158                    # blit the bitmap to the screen
159                    dc.BeginDrawing()
160                    dc.DrawBitmap(self.bitmap, 0, 0)
161                    dc.EndDrawing()
162          else:          else:
163              # 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
164              # the screen.              # the screen.
# Line 364  class MapCanvas(wxWindow, Publisher): Line 168  class MapCanvas(wxWindow, Publisher):
168              # only thing we may have to do is to call self.Refresh()              # only thing we may have to do is to call self.Refresh()
169              # with a true argument in the right places.              # with a true argument in the right places.
170              dc.BeginDrawing()              dc.BeginDrawing()
171                dc.SetBackground(self.backgroundColor)
172              dc.Clear()              dc.Clear()
173              dc.EndDrawing()              dc.EndDrawing()
174    
175      def do_redraw(self):      def OnIdle(self, event):
176          # This should only be called if we have a non-empty map.          # render the screen if necessary
177    
178          # Get the window size.          if self.bitmap != -1:
179          width, height = self.GetSizeTuple()              return
180    
181            wxBeginBusyCursor()
182            try:
183                width, height = self.GetSizeTuple()
184    
         # If self.bitmap's still there, reuse it. Otherwise redraw it  
         if self.bitmap is not None:  
             bitmap = self.bitmap  
         else:  
185              bitmap = wx.wxEmptyBitmap(width, height)              bitmap = wx.wxEmptyBitmap(width, height)
186              dc = wx.wxMemoryDC()              dc = wx.wxMemoryDC()
187              dc.SelectObject(bitmap)              dc.SelectObject(bitmap)
188              dc.BeginDrawing()              dc.BeginDrawing()
189    
190              # clear the background              dc.SetBackground(self.backgroundColor)
191              dc.SetBrush(wx.wxWHITE_BRUSH)              dc.Clear()
             dc.SetPen(wx.wxTRANSPARENT_PEN)  
             dc.DrawRectangle(0, 0, width, height)  
192    
193              selected_layer = self.selection.SelectedLayer()              selected_layer = self.selection.SelectedLayer()
194              selected_shapes = self.selection.SelectedShapes()              selected_shapes = self.selection.SelectedShapes()
# Line 395  class MapCanvas(wxWindow, Publisher): Line 198  class MapCanvas(wxWindow, Publisher):
198    
199              # Pass the entire bitmap as update region to the renderer.              # Pass the entire bitmap as update region to the renderer.
200              # We're redrawing the whole bitmap, after all.              # We're redrawing the whole bitmap, after all.
201              renderer.RenderMap(self.map, (0, 0, width, height),              renderer.RenderMap(self.Map(), (0, 0, width, height),
202                                 selected_layer, selected_shapes)                                 selected_layer, selected_shapes)
203    
204              dc.EndDrawing()              dc.EndDrawing()
205              dc.SelectObject(wx.wxNullBitmap)              dc.SelectObject(wx.wxNullBitmap)
206    
207              self.bitmap = bitmap              self.bitmap = bitmap
208            finally:
209                wxEndBusyCursor()
210                pass
211    
212            # This causes a paint event that then draws the bitmap
213            self.redraw()
214    
215        def Export(self):
216    
217            if hasattr(self, "export_path"):
218                export_path = self.export_path
219            else:
220                export_path="."
221            dlg = wxFileDialog(self, _("Export Map"), export_path, "",
222                               "Enhanced Metafile (*.wmf)|*.wmf",
223                               wxSAVE|wxOVERWRITE_PROMPT)
224            if dlg.ShowModal() == wxID_OK:
225                self.export_path = os.path.dirname(dlg.GetPath())
226                dc = wxMetaFileDC(dlg.GetPath())
227        
228                scale, offset, mapregion = output_transform(self.scale,
229                                                            self.offset,
230                                                            self.GetSizeTuple(),
231                                                            dc.GetSizeTuple())
232    
233          # blit the bitmap to the screen              selected_layer = self.selection.SelectedLayer()
234          dc = wx.wxMemoryDC()              selected_shapes = self.selection.SelectedShapes()
         dc.SelectObject(bitmap)  
         clientdc = wxClientDC(self)  
         clientdc.BeginDrawing()  
         clientdc.Blit(0, 0, width, height, dc, 0, 0)  
         clientdc.EndDrawing()  
235    
236                renderer = ExportRenderer(dc, scale, offset)
237    
238                # Pass the entire bitmap as update region to the renderer.
239                # We're redrawing the whole bitmap, after all.
240                width, height = self.GetSizeTuple()
241                renderer.RenderMap(self.Map(),
242                                    (0,0,
243                                        (width/self.scale)*scale,
244                                        (height/self.scale)*scale),
245                                    mapregion,
246                                    selected_layer, selected_shapes)
247                dc.EndDrawing()
248                dc.Close()
249            dlg.Destroy()
250            
251      def Print(self):      def Print(self):
252          printer = wx.wxPrinter()          printer = wx.wxPrinter()
253          printout = MapPrintout(self.map)          width, height = self.GetSizeTuple()
254          printer.Print(self, printout, wx.true)          selected_layer = self.selection.SelectedLayer()
255            selected_shapes = self.selection.SelectedShapes()
256            
257            printout = MapPrintout(self, self.Map(), (0, 0, width, height),
258                                   selected_layer, selected_shapes)
259            printer.Print(self, printout, True)
260          printout.Destroy()          printout.Destroy()
261    
     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  
   
262      def redraw(self, *args):      def redraw(self, *args):
263          self.Refresh(0)          self.Refresh(False)
264    
265      def full_redraw(self, *args):      def full_redraw(self, *args):
266          self.bitmap = None          self.bitmap = None
267          self.redraw()          self.redraw()
268    
269      def projection_changed(self, *args):      def map_projection_changed(self, map, old_proj):
270          self.FitMapToWindow()          ViewPort.map_projection_changed(self, map, old_proj)
271          self.full_redraw()          self.full_redraw()
272    
273      def set_view_transform(self, scale, offset):      def layer_projection_changed(self, *args):
274          self.scale = scale          ViewPort.layer_projection_changed(self, args)
         self.offset = offset  
275          self.full_redraw()          self.full_redraw()
276    
277      def proj_to_win(self, x, y):      def set_view_transform(self, scale, offset):
278          """\          ViewPort.set_view_transform(self, scale, offset)
279          Return the point in  window coords given by projected coordinates x y          self.full_redraw()
         """  
         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 with or zero height. Do Nothing  
             return  
         scalex = width / (urx - llx)  
         scaley = height / (ury - lly)  
         scale = min(scalex, scaley)  
         offx = 0.5 * (width - (urx + llx) * scale)  
         offy = 0.5 * (height + (ury + lly) * scale)  
         self.set_view_transform(scale, (offx, offy))  
   
     def FitMapToWindow(self):  
         """Fit the map to the window  
   
         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 ZoomFactor(self, factor, center = None):  
         """Multiply the zoom by factor and center on center.  
   
         The optional parameter center is a point in window coordinates  
         that should be centered. If it is omitted, it defaults to the  
         center of the window  
         """  
         width, height = self.GetSizeTuple()  
         scale = self.scale * factor  
         offx, offy = self.offset  
         if center is not None:  
             cx, cy = center  
         else:  
             cx = width / 2  
             cy = height / 2  
         offset = (factor * (offx - cx) + width / 2,  
                   factor * (offy - cy) + height / 2)  
         self.set_view_transform(scale, offset)  
   
     def ZoomOutToRect(self, rect):  
         """Zoom out to fit the currently visible region into rect.  
   
         The rect parameter is given in window coordinates  
         """  
         # determine the bbox of the displayed region in projected  
         # coordinates  
         width, height = self.GetSizeTuple()  
         llx, lly = self.win_to_proj(0, height - 1)  
         urx, ury = self.win_to_proj(width - 1, 0)  
   
         sx, sy, ex, ey = rect  
         scalex = (ex - sx) / (urx - llx)  
         scaley = (ey - sy) / (ury - lly)  
         scale = min(scalex, scaley)  
   
         offx = 0.5 * ((ex + sx) - (urx + llx) * scale)  
         offy = 0.5 * ((ey + sy) + (ury + lly) * scale)  
         self.set_view_transform(scale, (offx, offy))  
   
     def Translate(self, dx, dy):  
         """Move the map by dx, dy pixels"""  
         offx, offy = self.offset  
         self.set_view_transform(self.scale, (offx + dx, offy + dy))  
   
     def SelectTool(self, tool):  
         """Make tool the active tool.  
   
         The parameter should be an instance of Tool or None to indicate  
         that no tool is active.  
         """  
         self.tool = tool  
   
     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))  
   
     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.  
   
         The result is a 2-tuple of floats with the coordinates. If the  
         mouse is not in the window, the result is None.  
         """  
         if self.current_position is not None:  
             x, y = self.current_position  
             return self.win_to_proj(x, y)  
         else:  
             return None  
   
     def set_current_position(self, event):  
         """Set the current position from event  
280    
281          Should be called by all events that contain mouse positions      def GetPortSizeTuple(self):
282          especially EVT_MOTION. The event paramete may be None to          return self.GetSizeTuple()
         indicate the the pointer left the window.  
         """  
         if event is not None:  
             self.current_position = (event.m_x, event.m_y)  
         else:  
             self.current_position = None  
         self.issue(VIEW_POSITION)  
283    
284      def OnLeftDown(self, event):      def OnLeftDown(self, event):
285          self.set_current_position(event)          self.MouseLeftDown(event)
286          if self.tool is not None:          if self.tool is not None:
287              self.drag_dc = wxClientDC(self)              self.drag_dc = wxClientDC(self)
288              self.drag_dc.SetLogicalFunction(wxINVERT)              self.drag_dc.SetLogicalFunction(wxINVERT)
289              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
             self.CaptureMouse()  
             self.tool.MouseDown(event)  
290              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
291                self.CaptureMouse()
292              self.dragging = 1              self.dragging = 1
293    
294      def OnLeftUp(self, event):      def OnLeftUp(self, event):
295          self.set_current_position(event)          self.MouseLeftUp(event)
296          if self.dragging:          if self.dragging:
297              self.ReleaseMouse()              self.ReleaseMouse()
298              try:              try:
299                  self.tool.Hide(self.drag_dc)                  self.tool.Hide(self.drag_dc)
                 self.tool.MouseUp(event)  
300              finally:              finally:
301                  self.drag_dc = None                  self.drag_dc = None
302                  self.dragging = 0                  self.dragging = 0
303    
304      def OnMotion(self, event):      def OnMotion(self, event):
         self.set_current_position(event)  
305          if self.dragging:          if self.dragging:
306              self.tool.Hide(self.drag_dc)              self.tool.Hide(self.drag_dc)
307              self.tool.MouseMove(event)  
308            self.MouseMove(event)
309    
310            if self.dragging:
311              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
312    
313      def OnLeaveWindow(self, event):      def OnLeaveWindow(self, event):
# Line 648  class MapCanvas(wxWindow, Publisher): Line 330  class MapCanvas(wxWindow, Publisher):
330          # FIXME: We should perhaps try to limit the redraw to the are          # FIXME: We should perhaps try to limit the redraw to the are
331          # actually covered by the shapes before and after the selection          # actually covered by the shapes before and after the selection
332          # change.          # change.
333            ViewPort.shape_selected(self, layer, shape)
334          self.full_redraw()          self.full_redraw()
335    
336      def unprojected_rect_around_point(self, x, y, dist):      def GetTextExtent(self, text):
337          """return a rect dist pixels around (x, y) in unprojected corrdinates          dc = wxClientDC(self)
338            font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
339          The return value is a tuple (minx, miny, maxx, maxy) suitable a          dc.SetFont(font)
340          parameter to a layer's ShapesInRegion method.          return dc.GetTextExtent(text)
         """  
         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)  
   
         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  
341    
342      def LabelShapeAt(self, x, y):      def LabelShapeAt(self, x, y, text=None):
343          """Add or remove a label at window position x, y.          """Add or remove a label at window position x, y.
344    
345          If there's a label at the given position, remove it. Otherwise          If there's a label at the given position, remove it. Otherwise
346          determine the shape at the position, run the label dialog and          determine the shape at the position, run the label dialog and
347          unless the user cancels the dialog, add a laber.          unless the user cancels the dialog, add a label.
348          """          """
         ox = x; oy = y  
349          label_layer = self.map.LabelLayer()          label_layer = self.map.LabelLayer()
350          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
351          if layer is None and shape_index is not None:          if layer is None and shape_index is not None:
352              # a label was selected              ViewPort.LabelShapeAt(self, x, y)
             label_layer.RemoveLabel(shape_index)  
353          elif layer is not None:          elif layer is not None:
354              text = labeldialog.run_label_dialog(self, layer.table, shape_index)              text = labeldialog.run_label_dialog(self,
355              if text:                                                  layer.ShapeStore().Table(),
356                  proj = self.map.projection                                                  shape_index)
357                  if proj is not None:              ViewPort.LabelShapeAt(self, x, y, text)
                     map_proj = proj  
                 else:  
                     map_proj = None  
                 proj = layer.projection  
                 if proj is not None:  
                     layer_proj = proj  
                 else:  
                     layer_proj = None  
   
                 shapetype = layer.ShapeType()  
                 if shapetype == SHAPETYPE_POLYGON:  
                     x, y = shape_centroid(layer.shapefile.cobject(),  
                                           shape_index,  
                                           map_proj, layer_proj, 1, 1, 0, 0)  
                     if map_proj is not None:  
                         x, y = map_proj.Inverse(x, y)  
                 else:  
                     shape = layer.Shape(shape_index)  
                     if shapetype == SHAPETYPE_POINT:  
                         x, y = shape.Points()[0]  
                     else:  
                         # assume SHAPETYPE_ARC  
                         points = shape.Points()  
                         x, y = points[len(points) / 2]  
                     if layer_proj is not None:  
                         x, y = layer_proj.Inverse(x, y)  
                 if shapetype == SHAPETYPE_POINT:  
                     halign = ALIGN_LEFT  
                     valign = ALIGN_CENTER  
                 elif shapetype == SHAPETYPE_POLYGON:  
                     halign = ALIGN_CENTER  
                     valign = ALIGN_CENTER  
                 elif shapetype == SHAPETYPE_ARC:  
                     halign = ALIGN_LEFT  
                     valign = ALIGN_CENTER  
                 label_layer.AddLabel(x, y, text,  
                                      halign = halign, valign = valign)  

Legend:
Removed from v.610  
changed lines
  Added in v.1460

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26